Short form for keyword arguments and dicts
Keyword arguments are great for increasing readability and making code more robust but in my opinion they are underused compared to the gains they can provide. You often end up with code like: foo(bar=bar, baz=baz, foobaz=foobaz) which is less readable than the ordered argument version when the names of the variables and the keywords match. ( Here's another guy pointing out the same thing: http://stackoverflow.com/questions/7041752/any-reason-not-to-always-use-keyw... ) I have a suggestion that I believe can enable more usage of keyword arguments while still retaining almost all the brevity of ordered arguments: if the variable name to the right of the equal sign equals the keyword argument ("foo=foo") make it optional to just specify the name once ("=foo"). For completeness I suggest also make the same change for dictionaries: {'foo': foo} -> {:foo}. This change would turn the following code: a = 1 b = 2 c = 3 d = {'a':a, 'b':b, 'c':c} foo(a=a, b=b, c=c) into: a = 1 b = 2 c = 3 d = {:a, :b, :c} foo(=a, =b, =c) This should be compatible with existing code bases since the new forms are syntax errors in current python. What do you think? / Anders Hovmöller
On 22/06/2013 11:27, Anders Hovmöller wrote:
Keyword arguments are great for increasing readability and making code more robust but in my opinion they are underused compared to the gains they can provide. You often end up with code like:
foo(bar=bar, baz=baz, foobaz=foobaz)
which is less readable than the ordered argument version when the names of the variables and the keywords match. ( Here's another guy pointing out the same thing: http://stackoverflow.com/questions/7041752/any-reason-not-to-always-use-keyw...)
I have a suggestion that I believe can enable more usage of keyword arguments while still retaining almost all the brevity of ordered arguments: if the variable name to the right of the equal sign equals the keyword argument ("foo=foo") make it optional to just specify the name once ("=foo"). For completeness I suggest also make the same change for dictionaries: {'foo': foo} -> {:foo}. This change would turn the following code:
a = 1 b = 2 c = 3 d = {'a':a, 'b':b, 'c':c} foo(a=a, b=b, c=c)
into:
a = 1 b = 2 c = 3 d = {:a, :b, :c}
Shouldn't that mean: d = {a:a, b:b, c:c}
foo(=a, =b, =c)
This should be compatible with existing code bases since the new forms are syntax errors in current python.
What do you think?
I'm not convinced.
Well no, because: foo(**dict(a=a, b=b, c=c)) foo(**{'a':a, 'b':b, 'c':c}) foo(a=a, b=b, c=c) are all the same thing. So the short forms should match in the same way: foo(**dict(=a, =b, =c)) foo(**{:a, :b, :c}) foo(=a, =b, =c) On Sat, Jun 22, 2013 at 8:26 PM, MRAB <python@mrabarnett.plus.com> wrote:
On 22/06/2013 11:27, Anders Hovmöller wrote:
Keyword arguments are great for increasing readability and making code more robust but in my opinion they are underused compared to the gains they can provide. You often end up with code like:
foo(bar=bar, baz=baz, foobaz=foobaz)
which is less readable than the ordered argument version when the names of the variables and the keywords match. ( Here's another guy pointing out the same thing: http://stackoverflow.com/**questions/7041752/any-reason-** not-to-always-use-keyword-**arguments#comment8553765_**7041986<http://stackoverflow.com/questions/7041752/any-reason-not-to-always-use-keyw...> )
I have a suggestion that I believe can enable more usage of keyword arguments while still retaining almost all the brevity of ordered arguments: if the variable name to the right of the equal sign equals the keyword argument ("foo=foo") make it optional to just specify the name once ("=foo"). For completeness I suggest also make the same change for dictionaries: {'foo': foo} -> {:foo}. This change would turn the following code:
a = 1 b = 2 c = 3 d = {'a':a, 'b':b, 'c':c} foo(a=a, b=b, c=c)
into:
a = 1 b = 2 c = 3 d = {:a, :b, :c}
Shouldn't that mean:
d = {a:a, b:b, c:c}
foo(=a, =b, =c)
This should be compatible with existing code bases since the new forms are syntax errors in current python.
What do you think?
I'm not convinced.
______________________________**_________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/**mailman/listinfo/python-ideas<http://mail.python.org/mailman/listinfo/python-ideas>
On 22 June 2013 20:23, Anders Hovmöller <boxed@killingar.net> wrote:
Well no, because:
foo(**dict(a=a, b=b, c=c)) foo(**{'a':a, 'b':b, 'c':c}) foo(a=a, b=b, c=c)
are all the same thing. So the short forms should match in the same way:
foo(**dict(=a, =b, =c)) foo(**{:a, :b, :c}) foo(=a, =b, =c)
What about: class Foo: bar = bar to class Foo: = bar ? I'm not convinced either. I like the idea, but it's not that big a deal and I don't like your proposed implementation. There are so many more cases to cover and this doesn't fill them, nor it's original one nicely.
Hmm, I wasn't aware that doing class Foo: bar = bar was even valid python, blech. But that's a pretty contrived example. I'm not suggesting something huge and radical like totally redefining how assignment works :P I'm just suggesting a small change that I believe would have repercussions far above the weight class of the change itself. "It's the little things" and all that.
I'm not convinced either. I like the idea, but it's not that big a deal and I don't like your proposed implementation.
Well I think it is a big deal. I think Objective-C code bases are much easier to maintain because they have a superior syntax for calling methods. I don't like it when other languages do something as simple as calling functions better than my otherwise favorite language :P
There are so many more cases to cover and this doesn't fill them,
Like what? At least name one so we can have a discussion about it!
nor it's original one nicely.
Ok. Why? I'm not saying you're wrong, I just want to know what the reasons are so I can understand why I was mistaken so I can forget about this idea :P best regards, Anders
On 22 June 2013 20:51, Anders Hovmöller <boxed@killingar.net> wrote:
Hmm, I wasn't aware that doing
class Foo: bar = bar
was even valid python, blech. But that's a pretty contrived example. I'm not suggesting something huge and radical like totally redefining how assignment works :P I'm just suggesting a small change that I believe would have repercussions far above the weight class of the change itself. "It's the little things" and all that.
Yes, but consistency, y'know. Why "bar = bar ≡ = bar" here and "bar = bar !≡ = bar" there?
I'm not convinced either. I like the idea, but it's not that big a deal and I don't like your proposed implementation.
Well I think it is a big deal. I think Objective-C code bases are much easier to maintain because they have a superior syntax for calling methods. I don't like it when other languages do something as simple as calling functions better than my otherwise favorite language :P
I've skimmed a bit but I'm still unsure; why? How does Objective-C deal with this?
There are so many more cases to cover and this doesn't fill them,
Like what? At least name one so we can have a discussion about it!
One? Well, the one above! I agree that classes seem a bit far-fetched (personally I dislike that syntax) but what about: def function(arg=arg): ... def function(arg): self.arg = arg thing = dictionary[thing] and so on, which are all of the same form, albeit with different "surroundings". We can't just double the whole syntax of Python for this!
nor it's original one nicely.
Ok. Why? I'm not saying you're wrong, I just want to know what the reasons are so I can understand why I was mistaken so I can forget about this idea :P
Does foo(=bar) not bug you? Really?
Yes, but consistency, y'know. Why "bar = bar ≡ = bar" here and "bar = bar !≡ = bar" there?
Keyword arguments aren't the same thing as assignment. The consistency ship has sailed and we're not on it :P
I've skimmed a bit but I'm still unsure; why? How does Objective-C deal with this?
They deal with it with the nuclear option: all methods are 100% of the time ordered AND named. Unfortunately this has the side effect of a lot of method calls like "[foo setX:x y:y width:width height:height font:font]" which is almost exactly the same as the worst case in python: "foo.setStuff(x=x, y=y, width=width, height=height, font=font)". This rather brutish approach forces a rather verbose style of writing, which is annoying when you just want to bang out a prototype but extremely valuable when having to maintain large code bases down the line. One of the main points about my suggestion is that there should be almost no overhead to using keyword arguments when just passing arguments along or when variable names are nice and descriptive. This would create a lower barrier to use, which in turn leads to more solid and more readable code (I hope!).
There are so many more cases to cover and this doesn't fill them,
Like what? At least name one so we can have a discussion about it!
One? Well, the one above!
I agree that classes seem a bit far-fetched (personally I dislike that syntax) but what about:
def function(arg=arg): ...
def function(arg): self.arg = arg
(Where is self defined?)
thing = dictionary[thing]
and so on, which are all of the same form, albeit with different "surroundings". We can't just double the whole syntax of Python for this!
I'm not suggesting that though. It seems to me like you're taking my suggestion in absurdum.
Does foo(=bar) not bug you? Really?
Compared to foo(bar=bar)? No.
On Jun 22, 2013, at 14:01, Anders Hovmöller <boxed@killingar.net> wrote:
I've skimmed a bit but I'm still unsure; why? How does Objective-C deal with this?
They deal with it with the nuclear option: all methods are 100% of the time ordered AND named. Unfortunately this has the side effect of a lot of method calls like "[foo setX:x y:y width:width height:height font:font]" which is almost exactly the same as the worst case in python: "foo.setStuff(x=x, y=y, width=width, height=height, font=font)".
This rather brutish approach forces a rather verbose style of writing, which is annoying when you just want to bang out a prototype but extremely valuable when having to maintain large code bases down the line.
You initially said that ObjC deals with this problem better than Python, and now you say that it's better because it forces you to use the keyword names (actually they're part of the method name, but let's ignore that) _always_, which Python only forces you to do it when not using them positionally. I don't understand why you're making this argument in support of a proposal that would make Python even less explicit about keyword names, less like ObjC, and, by your analysis, harder to maintain and therefore worse.
You initially said that ObjC deals with this problem better than Python, and now you say that it's better because it forces you to use the keyword names (actually they're part of the method name, but let's ignore that) _always_, which Python only forces you to do it when not using them positionally.
I don't understand why you're making this argument in support of a proposal that would make Python even less explicit about keyword names, less like ObjC, and, by your analysis, harder to maintain and therefore worse.
I think you and I are talking about different things when talking about "this problem". For me the problem is to avoid stuff like "foo(1, 'foo', None, 9, 'baz')", not avoid repeating names. I just believe that python has syntax that promotes positional arguments even when it makes the code worse. My suggestion might on the surface look like just a way to type less, but that misses the point. It's about shifting the balance towards keyword arguments.
On 23/06/13 18:22, Anders Hovmöller wrote:
I think you and I are talking about different things when talking about "this problem". For me the problem is to avoid stuff like "foo(1, 'foo', None, 9, 'baz')", not avoid repeating names.
Your suggestion doesn't do a thing to avoid code like the above, since all the arguments are literals. -- Steven
On Jun 23, 2013, at 1:22, Anders Hovmöller <boxed@killingar.net> wrote:
You initially said that ObjC deals with this problem better than Python, and now you say that it's better because it forces you to use the keyword names (actually they're part of the method name, but let's ignore that) _always_, which Python only forces you to do it when not using them positionally.
I don't understand why you're making this argument in support of a proposal that would make Python even less explicit about keyword names, less like ObjC, and, by your analysis, harder to maintain and therefore worse.
I think you and I are talking about different things when talking about "this problem". For me the problem is to avoid stuff like "foo(1, 'foo', None, 9, 'baz')", not avoid repeating names.
But your suggestion wouldn't affect that at all, as not a single one of the arguments is a variable, much less a variable with the same name as a keyword parameter. And I don't think it's a coincidence that you came up with a bad example--I think good examples are very rare.
I just believe that python has syntax that promotes positional arguments even when it makes the code worse. My suggestion might on the surface look like just a way to type less, but that misses the point. It's about shifting the balance towards keyword arguments.
I don't think it does. It shifts the balance toward creating unnecessary local variables instead of explicit keyword names. Let's look at your example again, five different ways: foo(1, 'foo', None, 9, 'baz') foo(bar=1, baz='foo', qux=None, spam=9, eggs='baz') bar, baz, qux, spam, eggs = 1, 'foo', None, 9, 'baz' foo(bar, baz, qux, spam, eggs) bar, baz, qux, spam, eggs = 1, 'foo', None, 9, 'baz' foo(bar=bar, baz=baz, qux=qux, spam=spam, eggs=eggs) bar, baz, qux, spam, eggs = 1, 'foo', None, 9, 'baz' foo(=bar, =baz, =qux, =spam, =eggs) I'll agree that the 5th is better than the 4th. But the 2nd and 3rd are also much better than the 4th, and the 5th. In particular, if your arguments are already in variables with the same name as the parameters, adding the keyword names doesn't add anything. That may not be so obvious with this silly example, so let's take a real example: In an expression like "Barrier(4, f, 5)", it's completely unclear what the arguments mean without reading the help. Even with "Barrier(len(threads), callback, 5)" it's not very clear. But with "Barrier(parties, action, timeout)" there's no confusion at all. Your suggestion would do nothing to encourage the use of keywords in the first two cases, where they're essential, but only in the last case, where they don't add any information. On top of that, if the most natural names for your variables do not match the keywords, your change would encourage renaming them just to access the syntactic sugar. The only common case where I see this being useful is in the construction of dict (and other mappings), and I think the alternative ideas described by (IIRC) Nick Coghlan are much more interesting for that use case. But just because I can't imagine it doesn't mean it's not real. If you can show some real-life code, or even realistic fake code, where there are variables that match the parameter names, and the author either used keywords leading to overly verbose code, or should have used them but didn't leading to confusing code, please offer up the examples.
In fact, going back to the SO comment you linked in your original answer, it argues against your idea for the same reason:
Indeed, keyword arguments are really useful when passing literals to a function. However, if the arguments are variables with clear enough names, it becomes very noisy. Consider: create_user(first_name=first_name, last_name=last_name, contact_email=contact_email, ...).
He's clearly saying that you should use keywords when they add information, and not use them when they add nothing but noise. Your suggestion would make them add _less_ noise in one particular case where they add nothing but noise, but that's not a problem that needs to be solved, because Python already has a solution: don't use keywords in that case. Adding the extra = before each parameter just makes things less readable without giving any new information, so why should we add syntax to encourage it? Sent from a random iPhone On Jun 23, 2013, at 2:41, Andrew Barnert <abarnert@yahoo.com> wrote:
On Jun 23, 2013, at 1:22, Anders Hovmöller <boxed@killingar.net> wrote:
You initially said that ObjC deals with this problem better than Python, and now you say that it's better because it forces you to use the keyword names (actually they're part of the method name, but let's ignore that) _always_, which Python only forces you to do it when not using them positionally.
I don't understand why you're making this argument in support of a proposal that would make Python even less explicit about keyword names, less like ObjC, and, by your analysis, harder to maintain and therefore worse.
I think you and I are talking about different things when talking about "this problem". For me the problem is to avoid stuff like "foo(1, 'foo', None, 9, 'baz')", not avoid repeating names.
But your suggestion wouldn't affect that at all, as not a single one of the arguments is a variable, much less a variable with the same name as a keyword parameter.
And I don't think it's a coincidence that you came up with a bad example--I think good examples are very rare.
I just believe that python has syntax that promotes positional arguments even when it makes the code worse. My suggestion might on the surface look like just a way to type less, but that misses the point. It's about shifting the balance towards keyword arguments.
I don't think it does. It shifts the balance toward creating unnecessary local variables instead of explicit keyword names. Let's look at your example again, five different ways:
foo(1, 'foo', None, 9, 'baz')
foo(bar=1, baz='foo', qux=None, spam=9, eggs='baz')
bar, baz, qux, spam, eggs = 1, 'foo', None, 9, 'baz' foo(bar, baz, qux, spam, eggs)
bar, baz, qux, spam, eggs = 1, 'foo', None, 9, 'baz' foo(bar=bar, baz=baz, qux=qux, spam=spam, eggs=eggs)
bar, baz, qux, spam, eggs = 1, 'foo', None, 9, 'baz' foo(=bar, =baz, =qux, =spam, =eggs)
I'll agree that the 5th is better than the 4th. But the 2nd and 3rd are also much better than the 4th, and the 5th.
In particular, if your arguments are already in variables with the same name as the parameters, adding the keyword names doesn't add anything.
That may not be so obvious with this silly example, so let's take a real example:
In an expression like "Barrier(4, f, 5)", it's completely unclear what the arguments mean without reading the help. Even with "Barrier(len(threads), callback, 5)" it's not very clear. But with "Barrier(parties, action, timeout)" there's no confusion at all.
Your suggestion would do nothing to encourage the use of keywords in the first two cases, where they're essential, but only in the last case, where they don't add any information.
On top of that, if the most natural names for your variables do not match the keywords, your change would encourage renaming them just to access the syntactic sugar.
The only common case where I see this being useful is in the construction of dict (and other mappings), and I think the alternative ideas described by (IIRC) Nick Coghlan are much more interesting for that use case.
But just because I can't imagine it doesn't mean it's not real. If you can show some real-life code, or even realistic fake code, where there are variables that match the parameter names, and the author either used keywords leading to overly verbose code, or should have used them but didn't leading to confusing code, please offer up the examples. _______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
He's clearly saying that you should use keywords when they add information, and not use them when they add nothing but noise. Your suggestion would make them add _less_ noise in one particular case where they add nothing but noise, but that's not a problem that needs to be solved, because Python already has a solution: don't use keywords in that case. Adding the extra = before each parameter just makes things less readable without giving any new information, so why should we add syntax to encourage it?
Because keyword arguments are less brittle. Consider the case you replied to: create_user(first_name=first_name, last_name=last_name, contact_email=contact_email) if you change it to: create_user(first_name, last_name, contact_email) it is more readable but it doesn't actually mean the same thing. The first piece of code will break in a nice way when the keyword argument list is changed in the definition of create_user. The second will probably fail, but later and in some not so nice way like suddenly you have emails in your database where you should've had addresses. Objective-C is better in this case because it strongly enforces something like keyword argument always. What I'm saying is that it'd be nice to be able to write code that uses keyword arguments 100% of the time for all function calls without making the readability worse.
On 23/06/13 21:17, Anders Hovmöller wrote:
Objective-C is better in this case because it strongly enforces something like keyword argument always. What I'm saying is that it'd be nice to be able to write code that uses keyword arguments 100% of the time for all function calls without making the readability worse.
Nobody is stopping you from using keyword arguments 100% of the time (except for built-in functions that don't accept keyword arguments, but most of them take only one or two arguments). Go right ahead. I love keyword arguments! But this discussion isn't about the pros and cons of keyword arguments. This discussion is about adding magic syntax for implicitly specifying the keyword parameter name when it happens to match an argument which is an expression consisting of a single name. With your suggestion, you can abbreviate this special case: create_user(first_name=first_name, last_name=last_name, contact_email=contact_email) with this: create_user(=first_name, =last_name, =contact_email) (which I consider too ugly for words), but it does absolutely nothing for: create_user(first_name=record[3], last_name=record[2], contact_email=record.email) create_user(first_name=personal_name, last_name=family_name, contact_email=email_address) create_user(first_name="Steven", last_name="D'Aprano", contact_email="steve@example.com") create_user(first_name=first_name.title(), last_name=last_name.title(), contact_email=validate_and_clean(contact_email)) The special case "parameter name matches exactly argument expression" is far too special, and the benefit far too minor, to deserve special syntax. Oh, one last thing... your suggestion is also brittle. If you refactor the variable name, or change the function parameter name, code using this shortcut will break. Parameter names are part of the function API and shouldn't change, but variable names are not, and should be free to change. With your suggestion, they can't. -- Steven
(which I consider too ugly for words), but it does absolutely nothing for:
create_user(first_name=record[**3], last_name=record[2], contact_email=record.email)
create_user(first_name=**personal_name, last_name=family_name, contact_email=email_address)
create_user(first_name="**Steven", last_name="D'Aprano", contact_email=" steve@example.**com <steve@example.com>")
create_user(first_name=first_**name.title(), last_name=last_name.title(), contact_email=validate_and_**clean(contact_email))
The special case "parameter name matches exactly argument expression" is far too special, and the benefit far too minor, to deserve special syntax.
I disagree. I think small things can have big impacts because the design of a system shapes the usage of the system.
Oh, one last thing... your suggestion is also brittle. If you refactor the variable name, or change the function parameter name, code using this shortcut will break.
Let's go through that statement. Refactoring variable names: yes, if you search/replace without checking the diff or using a tool that doesn't understand the syntax that'd probably screw it up. Which of course is true whenever you use the wrong tool for the wrong job and you're sloppy about it. The code will still break by pointing out that there's no such argument to the function which is better than positional arguments, and if you're sloppy about it you'd screw up "foo(bar=bar)" when trying to rename "bar". So that argument is pretty clearly moot. If you change the function parameter name: then all calls using keyword arguments will fail with a pretty good error message. This is 100% the same between python code today and with my shortcut. So again, moot.
Parameter names are part of the function API and shouldn't change, but variable names are not, and should be free to change. With your suggestion, they can't.
The transformation to the code when changing variables names will in some cases be bigger yes. Saying that variable names aren't free to change is hyperbole though. As for parameter names being part of an API, well yes, that's true, but code changes. Just saying that we should never change the parameter names of any function after it has been called once isn't what you meant right?
Steven D'Aprano wrote:
The special case "parameter name matches exactly argument expression" is far too special, and the benefit far too minor, to deserve special syntax.
It occurs more often than you might think, because taking parameters that you've been passed and passing them on to another function is a common pattern.
Oh, one last thing... your suggestion is also brittle. If you refactor the variable name, or change the function parameter name, code using this shortcut will break. Parameter names are part of the function API and shouldn't change, but variable names are not
But it's not just any variable, it's a parameter to your function, so it's not likely to change its name either. -- Greg
On Jun 23, 2013, at 16:34, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Steven D'Aprano wrote:
The special case "parameter name matches exactly argument expression" is far too special, and the benefit far too minor, to deserve special syntax.
It occurs more often than you might think, because taking parameters that you've been passed and passing them on to another function is a common pattern
Yes, I do that all the time. But I can't think of a single case where there's any benefit to using keyword arguments. When you're forwarding your parameters exactly, the keywords are pure noise. Reducing the noise a little bit isn't nearly as good as just not creating it in the first place. Let's look at a typical such case: a class that encapsulates and delegates to another class--say, str. You'll have a bunch of methods like this: def encode(self, encoding, errors): return self.wrapped_str.encode(encoding, errors) What would be gained by changing it to: def encode(self, encoding, errors): return self.wrapped_str.encode(encoding=encoding, errors=errors) Or: def encode(self, encoding, errors): return self.wrapped_str.encode(=encoding, =errors) The reason for using keyword arguments is that often the meaning of positional arguments is unclear without looking up the function. That clearly isn't the case here. The meaning of the encoding and errors arguments is exactly as obvious without the keywords as with them. So, while I'll agree that the third version may be better than the second, it's still worse than the first, and Python already allows the first. This is an attempt to solve a problem that doesn't exist, and it doesn't even succeed in the attempt.
Andrew Barnert wrote:
On Jun 23, 2013, at 16:34, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
It occurs more often than you might think, because taking parameters that you've been passed and passing them on to another function is a common pattern
Yes, I do that all the time. But I can't think of a single case where there's any benefit to using keyword arguments.
That's puzzling, because the benefits are the same as with any other call that benefits from keyword arguments. Do you doubt the usefulness of keyword arguments in general? -- Greg
From: Greg Ewing <greg.ewing@canterbury.ac.nz> Sent: Monday, June 24, 2013 4:49 PM
Andrew Barnert wrote:
On Jun 23, 2013, at 16:34, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
It occurs more often than you might think, because taking parameters that you've been passed and passing them on to another function is a common pattern
Yes, I do that all the time. But I can't think of a single case where there's any benefit to using keyword arguments.
That's puzzling, because the benefits are the same as with any other call that benefits from keyword arguments.
No they aren't. The main benefit that a call gets from keyword arguments is that, from the parameter names, you can tell what the arguments mean. When the arguments are already variables (parameters or locals) with the same name, you already have the same information, and there is no benefit from getting it twice. Compare: url = get_appdir_url(False, None, True) url = get_appdir_url(per_user=False, for_domain=None, create=True) This is a change I made earlier today in someone else's coded. The improvement is, I hope, unarguable. But here's another function call I _didn't_ change: def get_appdir_url(per_user, for_domain, create): return get_special_url('appdir', per_user, for_domain, create) Would this be at all improved by adding keywords? return get_special_url(special='appdir', per_user=per_user, for_domain=for_domain, create=create) That just adds noise, making it less readable, rather than more. Of course there's another benefit: Sometimes, using keywords lets you reorder the arguments to a way that makes more sense for your use case and/or skip defaulted parameters that you don't care about: gzip.GzipFile(fileobj=f, compressionlevel=1) Obviously, I'm all for using the keywords there as well. But the proposal doesn't affect cases like that.
Do you doubt the usefulness of keyword arguments in general?
Not at all. I only doubt the idea of encouraging people to use keyword arguments _everywhere_. Keywords make code more readable in some. cases, less readable in others. Blindly using keywords everywhere would make code overall less readable, just as blindly avoiding keywords everywhere. Most Python programmers today seem to do a decent job finding the right balance, and the language and library do a decent job helping them. Could that be better? Sure. But encouraging keywords everywhere would not make it better. And a proposal that's specifically intended to encourage using keywords in cases where they add nothing but noise would definitely not make it better. I've already granted that dict construction is a special case where this may not be true. (When is dict construction not a special case when it comes to keyword arguments?) But, as I said before, I don't think it's the right solution for that case—and, since then, at least two people have offered other ideas for that special case.
On 25/06/2013 01:58, Andrew Barnert wrote:
From: Greg Ewing <greg.ewing@canterbury.ac.nz>
Sent: Monday, June 24, 2013 4:49 PM
Andrew Barnert wrote:
On Jun 23, 2013, at 16:34, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
It occurs more often than you might think, because taking parameters that you've been passed and passing them on to another function is a common pattern
Yes, I do that all the time. But I can't think of a single case where there's any benefit to using keyword arguments.
That's puzzling, because the benefits are the same as with any other call that benefits from keyword arguments.
No they aren't.
The main benefit that a call gets from keyword arguments is that, from the parameter names, you can tell what the arguments mean.
When the arguments are already variables (parameters or locals) with the same name, you already have the same information, and there is no benefit from getting it twice.
Compare:
url = get_appdir_url(False, None, True) url = get_appdir_url(per_user=False, for_domain=None, create=True)
This is a change I made earlier today in someone else's coded. The improvement is, I hope, unarguable.
But here's another function call I _didn't_ change:
def get_appdir_url(per_user, for_domain, create): return get_special_url('appdir', per_user, for_domain, create)
Would this be at all improved by adding keywords?
return get_special_url(special='appdir', per_user=per_user, for_domain=for_domain, create=create)
That just adds noise, making it less readable, rather than more.
Of course there's another benefit: Sometimes, using keywords lets you reorder the arguments to a way that makes more sense for your use case and/or skip defaulted parameters that you don't care about:
gzip.GzipFile(fileobj=f, compressionlevel=1)
Obviously, I'm all for using the keywords there as well. But the proposal doesn't affect cases like that.
Do you doubt the usefulness of keyword arguments in general?
Not at all. I only doubt the idea of encouraging people to use keyword arguments _everywhere_.
Keywords make code more readable in some. cases, less readable in others. Blindly using keywords everywhere would make code overall less readable, just as blindly avoiding keywords everywhere.
Most Python programmers today seem to do a decent job finding the right balance, and the language and library do a decent job helping them. Could that be better? Sure. But encouraging keywords everywhere would not make it better.
And a proposal that's specifically intended to encourage using keywords in cases where they add nothing but noise would definitely not make it better.
I've already granted that dict construction is a special case where this may not be true. (When is dict construction not a special case when it comes to keyword arguments?) But, as I said before, I don't think it's the right solution for that case—and, since then, at least two people have offered other ideas for that special case.
Why not just add a single marker followed by the names, something like this: return get_special_url(special='appdir', =, per_user, for_domain, create)
On Jun 24, 2013, at 18:22, MRAB <python@mrabarnett.plus.com> wrote:
On 25/06/2013 01:58, Andrew Barnert wrote:
From: Greg Ewing <greg.ewing@canterbury.ac.nz>
Sent: Monday, June 24, 2013 4:49 PM
Andrew Barnert wrote:
On Jun 23, 2013, at 16:34, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
It occurs more often than you might think, because taking parameters that you've been passed and passing them on to another function is a common pattern
Yes, I do that all the time. But I can't think of a single case where there's any benefit to using keyword arguments.
That's puzzling, because the benefits are the same as with any other call that benefits from keyword arguments.
No they aren't.
The main benefit that a call gets from keyword arguments is that, from the parameter names, you can tell what the arguments mean.
When the arguments are already variables (parameters or locals) with the same name, you already have the same information, and there is no benefit from getting it twice.
Compare:
url = get_appdir_url(False, None, True) url = get_appdir_url(per_user=False, for_domain=None, create=True)
This is a change I made earlier today in someone else's coded. The improvement is, I hope, unarguable.
But here's another function call I _didn't_ change:
def get_appdir_url(per_user, for_domain, create): return get_special_url('appdir', per_user, for_domain, create)
Would this be at all improved by adding keywords?
return get_special_url(special='appdir', per_user=per_user, for_domain=for_domain, create=create)
That just adds noise, making it less readable, rather than more.
Of course there's another benefit: Sometimes, using keywords lets you reorder the arguments to a way that makes more sense for your use case and/or skip defaulted parameters that you don't care about:
gzip.GzipFile(fileobj=f, compressionlevel=1)
Obviously, I'm all for using the keywords there as well. But the proposal doesn't affect cases like that.
Do you doubt the usefulness of keyword arguments in general?
Not at all. I only doubt the idea of encouraging people to use keyword arguments _everywhere_.
Keywords make code more readable in some. cases, less readable in others. Blindly using keywords everywhere would make code overall less readable, just as blindly avoiding keywords everywhere.
Most Python programmers today seem to do a decent job finding the right balance, and the language and library do a decent job helping them. Could that be better? Sure. But encouraging keywords everywhere would not make it better.
And a proposal that's specifically intended to encourage using keywords in cases where they add nothing but noise would definitely not make it better.
I've already granted that dict construction is a special case where this may not be true. (When is dict construction not a special case when it comes to keyword arguments?) But, as I said before, I don't think it's the right solution for that case—and, since then, at least two people have offered other ideas for that special case. Why not just add a single marker followed by the names, something like this:
return get_special_url(special='appdir', =, per_user, for_domain, create)
That's less ugly, but I still don't see what the benefit is. The code that already works in Python 3.3 (and even 2.x) has the same amount of information (again, it's already obvious what the parameters mean without using keywords), and it's even less ugly, and it doesn't require any new syntax. I suppose if someone later reorders the parameters of get_special_url without making any other change to the API, my code will still work. But how often does that happen? And I'm not sure a language change that encourages such API changes is a good thing.
On Jun 24, 2013, at 7:51 PM, Andrew Barnert <abarnert@yahoo.com> wrote:
I suppose if someone later reorders the parameters of get_special_url without making any other change to the API, my code will still work. But how often does that happen? And I'm not sure a language change that encourages such API changes is a good thing.
An library implementor can already write code that allows him to have keyword-only arguments, so he can avoid polluting the compatibility he must maintain in his positional arguments. It's less about allowing him to reorder them, and more about separating the positional and keyword arguments. I've not seen a solution I'm totally sold on yet, but supporting keyword argument shorthand seems like a good idea to me.
On 06/24/2013 06:22 PM, MRAB wrote:
Why not just add a single marker followed by the names, something like this:
return get_special_url(special='appdir', =, per_user, for_domain, create)
Interesting idea. Similar to using the '*' to only allow keyword arguments. Rather than having '*' in the `def` and '=' at the call-site, why not use the '*' in both places? Then it would mean 'only keyword-arguments allowed' and 'these names are the keyword arguments'. Because the variable names are the same as the keyword names we're not losing anything. -- ~Ethan~
The main benefit that a call gets from keyword arguments is that, from the parameter names, you can tell what the arguments mean.
Agreed.
When the arguments are already variables (parameters or locals) with the same name, you already have the same information, and there is no benefit from getting it twice.
Absolutely not. At that point you have to make a pretty big assumption that this is the case. In order to KNOW you need to go look up the function and compare two lists of names. And if it changes keyword arguments will throw an error upon invocation, positional arguments will not.
Keywords make code more readable in some. cases, less readable in others. Blindly using keywords everywhere would make code overall less readable, just as blindly avoiding keywords everywhere.
Currently a big part of why it makes code less readable is the repetition. It'd be cool to be able to get the advantages without making any significant dent in readability.
Most Python programmers today seem to do a decent job finding the right balance, and the language and library do a decent job helping them. Could that be better? Sure. But encouraging keywords everywhere would not make it better.
Just want to point out that you have no idea of knowing that. Making religious assertions doesn't strengthen your case. You could have pointed out that the current python culture is one that generally produces some of the best and most usable code already, making changes to that dynamic risky. THAT I would have agreed to.
And a proposal that's specifically intended to encourage using keywords in cases where they add nothing but noise would definitely not make it better.
Except of course the little detail that with my suggestion the added noise would be almost insignificant. 1 extra character per variable name. Only for really horrible code with variable names of 1 or 2 characters will that be a significant increase in "noise".
From: Anders Hovmöller <boxed@killingar.net> Sent: Monday, June 24, 2013 11:25 PM Before getting to the specific replies, I'd like to repeat my call for some examples of real (or at least realistic) code that would actually benefit from this change. I've already agreed that the two special cases of dict construction and str.format could benefit—but those are special cases. Nick Coghlan and others have already suggested they could be better handled with different improvements. Maybe they're wrong; maybe those special cases are important enough, and there is no better solution for them. But you're presenting this as a broader fix than that, and I don't see it. The only other real examples I've seen are my own cases of delegating functions that forward their parameters exactly, and I don't think those need to be improved. Maybe there's a lot more to your idea than I and others are seeing. You're clearly a knowledgeable and sane programmer, not some crank, and I believe that you do have real code you'd like to improve. But I can't imagine it, so I'd like you to demonstrate that real code.
When the arguments are already variables (parameters or locals) with the same name, you already have the same information, and there is no benefit from getting it twice.
Absolutely not. At that point you have to make a pretty big assumption that this is the case.
An assumption? Sure. A big one? No. When I'm reading this code: url = get_appdir_url(per_user, for_domain, create) … I have to assume that per_user is some kind of flag value that selects between a per-user appdir and a local-system appdir, not an integer that specifies the primary group ID. Just as I have to assume that get_appdir_url returns a URL to an appdir, not a pandas table full of the populations of world capitals. In other words, I have to assume that whoever wrote the code I'm reading isn't being malicious or psychotic when coming up with names. And again, this is totally different from the paradigm case for keyword arguments: url = get_appdir_url(False, None, True) Here, maybe I can guess that one of those two boolean values might be a per-user/local-system flag, but I have no idea which one, or whether False means local-system or per-user. So, using keywords makes a _huge_ difference. But in the previous case, I always know what per_user means. And, in fact, adding keywords doesn't affect what I have to assume. When I see this: url = get_appdir_url(per_user=per_user, for_domain=for_domain, create=create) … I _still_ have to assume that per_user means what I think it means. It's the same words, with the same meaning. And adding an "=" prefix to each argument wouldn't add any meaning either; it's just a meaningless symbol that I have to pass over to read the actual meaning of the code.
In order to KNOW you need to go look up the function and compare two lists of names.
No, in order to KNOW I'd need to pore over the implementation of the function, and either prove that it does what I expect, or test it sufficiently to my satisfaction. Fortunately, I don't usually need that kind of knowledge when reading someone's code. If I want to understand what your script does, or expand its functionality, I read it with the assumption that each expression means what it says. I may come back and look at some of them more carefully if I find a bug, or a clever hack that I don't understand, but that's not the usual case when reading code.
And if it changes keyword arguments will throw an error upon invocation, positional arguments will not.
How many times has it happened that some function changed the order of its parameters, but didn't change anything else that would break your code? Maybe once or twice in your lifetime as a programmer? Is it really common enough to be worth making your code less readable, even a little bit, to protect against it?
Most Python programmers today seem to do a decent job finding the right balance, and the language and library do a decent job helping them. Could that be better? Sure. But encouraging keywords everywhere would not make it better.
Just want to point out that you have no idea of knowing that.
Sure I do. And so do you. If you didn't have some sense, based on experience reading and writing lots of code in Python and other languages, about how well Python programmers take advantage of the freedom to choose between positional and keyword arguments, you wouldn't have proposed a change in the first place. And this is the key question. You're supporting the general principle that people should use keyword arguments whenever possible—replacing flexibility with TOOWTDI dogma. That can be a good thing when the flexibility has been poorly used (e.g., Python no longer lets you choose whether to explicitly or implicitly decode byte strings), or it can be a bad thing when the flexibility leads to better code. A big part of the reason both you and I use Python is that people have argued out these kinds of questions, instead of just assuming that there's no way of knowing and choosing arbitrarily.
Except of course the little detail that with my suggestion the added noise would be almost insignificant. 1 extra character per variable name. Only for really horrible code with variable names of 1 or 2 characters will that be a significant increase in "noise".
I realize this is almost like Godwin's Law here, but… By the exact same argument, perl sigils are insignificant. But everyone knows they're not. When I see "$remaining = shift(@ARGV);" instead of just "remaining = shift(ARGV);", it disturbs the flow of reading code, despite only being 1 extra character per variable name. That's what people mean when they say it's "ugly"—it's not about typographical beauty, it's about being able to look at code and quickly understand what it's doing. Consider the fact that Python doesn't require parens around conditions, or that it does require colons in block-introducing statements. Those are even tinier—one characters for the whole statement—and yet they make a huge difference in readability. There's always a cost in adding extra noise, and just saying "It's not that bad" isn't a good argument. The question is whether the benefit outweighs the cost. The **kw syntax is a stumbling block that every immigrant from JavaScript or C++ runs into at some point—first they have to figure out what it means, then they have to get used to it. But it's so hugely useful that the cost is obviously worth taking. Your =keyword syntax would have the same cost. Would it have a similarly large benefit?
Thank you for that long and thoughtful message. Unfortunately the big hairy examples where I believe something like my suggestion would be good are stuff I'm sitting and looking right now which of course is closed source so I can't share it with you :( Maybe it's just this code base that's pathological or maybe I'm just being paranoid about stuff not matching up. I'm gonna think about these possibilities for a while and give up on convincing you guys for now at least :P On Tue, Jun 25, 2013 at 11:14 AM, Andrew Barnert <abarnert@yahoo.com> wrote:
From: Anders Hovmöller <boxed@killingar.net> Sent: Monday, June 24, 2013 11:25 PM
Before getting to the specific replies, I'd like to repeat my call for some examples of real (or at least realistic) code that would actually benefit from this change.
I've already agreed that the two special cases of dict construction and str.format could benefit—but those are special cases. Nick Coghlan and others have already suggested they could be better handled with different improvements. Maybe they're wrong; maybe those special cases are important enough, and there is no better solution for them. But you're presenting this as a broader fix than that, and I don't see it. The only other real examples I've seen are my own cases of delegating functions that forward their parameters exactly, and I don't think those need to be improved.
Maybe there's a lot more to your idea than I and others are seeing. You're clearly a knowledgeable and sane programmer, not some crank, and I believe that you do have real code you'd like to improve. But I can't imagine it, so I'd like you to demonstrate that real code.
When the arguments are already variables (parameters or locals) with the same name, you already have the same information, and there is no benefit from getting it twice.
Absolutely not. At that point you have to make a pretty big assumption that this is the case.
An assumption? Sure. A big one? No.
When I'm reading this code:
url = get_appdir_url(per_user, for_domain, create)
… I have to assume that per_user is some kind of flag value that selects between a per-user appdir and a local-system appdir, not an integer that specifies the primary group ID. Just as I have to assume that get_appdir_url returns a URL to an appdir, not a pandas table full of the populations of world capitals. In other words, I have to assume that whoever wrote the code I'm reading isn't being malicious or psychotic when coming up with names.
And again, this is totally different from the paradigm case for keyword arguments:
url = get_appdir_url(False, None, True)
Here, maybe I can guess that one of those two boolean values might be a per-user/local-system flag, but I have no idea which one, or whether False means local-system or per-user. So, using keywords makes a _huge_ difference.
But in the previous case, I always know what per_user means. And, in fact, adding keywords doesn't affect what I have to assume. When I see this:
url = get_appdir_url(per_user=per_user, for_domain=for_domain, create=create)
… I _still_ have to assume that per_user means what I think it means. It's the same words, with the same meaning. And adding an "=" prefix to each argument wouldn't add any meaning either; it's just a meaningless symbol that I have to pass over to read the actual meaning of the code.
In order to KNOW you need to go look up the function and compare two lists of names.
No, in order to KNOW I'd need to pore over the implementation of the function, and either prove that it does what I expect, or test it sufficiently to my satisfaction.
Fortunately, I don't usually need that kind of knowledge when reading someone's code. If I want to understand what your script does, or expand its functionality, I read it with the assumption that each expression means what it says. I may come back and look at some of them more carefully if I find a bug, or a clever hack that I don't understand, but that's not the usual case when reading code.
And if it changes keyword arguments will throw an error upon invocation, positional arguments will not.
How many times has it happened that some function changed the order of its parameters, but didn't change anything else that would break your code? Maybe once or twice in your lifetime as a programmer? Is it really common enough to be worth making your code less readable, even a little bit, to protect against it?
Most Python programmers today seem to do a decent job finding the right balance, and the language and library do a decent job helping them. Could that be better? Sure. But encouraging keywords everywhere would not make it better.
Just want to point out that you have no idea of knowing that.
Sure I do. And so do you. If you didn't have some sense, based on experience reading and writing lots of code in Python and other languages, about how well Python programmers take advantage of the freedom to choose between positional and keyword arguments, you wouldn't have proposed a change in the first place.
And this is the key question. You're supporting the general principle that people should use keyword arguments whenever possible—replacing flexibility with TOOWTDI dogma. That can be a good thing when the flexibility has been poorly used (e.g., Python no longer lets you choose whether to explicitly or implicitly decode byte strings), or it can be a bad thing when the flexibility leads to better code. A big part of the reason both you and I use Python is that people have argued out these kinds of questions, instead of just assuming that there's no way of knowing and choosing arbitrarily.
Except of course the little detail that with my suggestion the added noise would be almost insignificant. 1 extra character per variable name. Only for really horrible code with variable names of 1 or 2 characters will that be a significant increase in "noise".
I realize this is almost like Godwin's Law here, but…
By the exact same argument, perl sigils are insignificant. But everyone knows they're not. When I see "$remaining = shift(@ARGV);" instead of just "remaining = shift(ARGV);", it disturbs the flow of reading code, despite only being 1 extra character per variable name. That's what people mean when they say it's "ugly"—it's not about typographical beauty, it's about being able to look at code and quickly understand what it's doing.
Consider the fact that Python doesn't require parens around conditions, or that it does require colons in block-introducing statements. Those are even tinier—one characters for the whole statement—and yet they make a huge difference in readability.
There's always a cost in adding extra noise, and just saying "It's not that bad" isn't a good argument. The question is whether the benefit outweighs the cost. The **kw syntax is a stumbling block that every immigrant from JavaScript or C++ runs into at some point—first they have to figure out what it means, then they have to get used to it. But it's so hugely useful that the cost is obviously worth taking. Your =keyword syntax would have the same cost. Would it have a similarly large benefit?
On Tue, Jun 25, 2013 at 7:54 PM, Anders Hovmöller <boxed@killingar.net> wrote:
Maybe it's just this code base that's pathological or maybe I'm just being paranoid about stuff not matching up. I'm gonna think about these possibilities for a while and give up on convincing you guys for now at least :P
Just a side idea, maybe what you want is not new syntax but a linter? Knock together a script that runs through your code and tells you about any oddities it finds, thus guaranteeing that your stuff does indeed match up. As an added bonus, you could plug that into your source control system so you get an alert before you can commit - not sure how you do that in Mercurial but I'm sure you can (I've only ever done it with git). Makes it really easy to catch problems. ChrisA
Just a side idea, maybe what you want is not new syntax but a linter? Knock together a script that runs through your code and tells you about any oddities it finds, thus guaranteeing that your stuff does indeed match up. As an added bonus, you could plug that into your source control system so you get an alert before you can commit - not sure how you do that in Mercurial but I'm sure you can (I've only ever done it with git). Makes it really easy to catch problems.
I've thought about that but rejected it because then I'd have to change all these functions to be keyword arguments. In an example I just scrolled to randomly this changes a function call from 368 characters to 627! I believe there are several worse examples :( With my suggestion at least that function call would only go up to 595 without changing local variable names. If I also add the feature to my suggestion that "foo(bar=something.bar)" == "foo(=something.bar)" (which is pretty horrible!), I can get it down to 500 and still use keyword arguments. And there'a few superflous Nones that I can get rid of if I use keyword arguments, but only 5, which in this case doesn't change much. Writing these numbers gives me the feeling that it's indeed this code base that is pathological :P If you know of an automated tool to convert functions from positional arguments to keyword arguments that'd be a fun experiment to run :P
On 25 June 2013 20:19, Anders Hovmöller <boxed@killingar.net> wrote:
Just a side idea, maybe what you want is not new syntax but a linter? Knock together a script that runs through your code and tells you about any oddities it finds, thus guaranteeing that your stuff does indeed match up. As an added bonus, you could plug that into your source control system so you get an alert before you can commit - not sure how you do that in Mercurial but I'm sure you can (I've only ever done it with git). Makes it really easy to catch problems.
I've thought about that but rejected it because then I'd have to change all these functions to be keyword arguments. In an example I just scrolled to randomly this changes a function call from 368 characters to 627! I believe there are several worse examples :(
With my suggestion at least that function call would only go up to 595 without changing local variable names. If I also add the feature to my suggestion that "foo(bar=something.bar)" == "foo(=something.bar)" (which is pretty horrible!), I can get it down to 500 and still use keyword arguments. And there'a few superflous Nones that I can get rid of if I use keyword arguments, but only 5, which in this case doesn't change much.
Writing these numbers gives me the feeling that it's indeed this code base that is pathological :P
Unfortunately, it sounds like that may be the case. The subprocess.Popen constructor is probably the most pathological "Swiss army function" in the standard library, and even that would struggle to hit 300 characters for a single function call (maybe if you had some long variable names to pass in, or wrote a long command line in place as a list or string literal). With function signatures like that, you may even want to build the keyword argument mapping programmatically, and then call the end result as: function_with_crazy_signature(**kwargs) It's not *that* uncommon for even subprocess.Popen to be called that way (or else for it to be wrapped in a helper class or closure that supplies some of the parameters). Building up to the final call with functools.partial is another way to potentially manage interacting with that kind of complicated API. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On Mon, Jun 24, 2013 at 11:34:49AM +1200, Greg Ewing wrote:
Steven D'Aprano wrote:
The special case "parameter name matches exactly argument expression" is far too special, and the benefit far too minor, to deserve special syntax.
It occurs more often than you might think, because taking parameters that you've been passed and passing them on to another function is a common pattern.
Another use case where this would come in handy is with string formatting:
print('{spam} and {eggs}'.format(spam=spam, eggs=eggs))
I've seen people use an awful workaround for this:
print('{spam} and {eggs}'.format(locals()))
While it looks a little magical, the proposed syntax would be an improvement (especially when there are many arguments):
print('{spam} and {eggs}'.format(=spam, =eggs))
I'm not sure if the proposed solution is necessarily the best, but it's not as contrived as some commenters have made it out to be. -- Andrew McNabb http://www.mcnabbs.org/andrew/ PGP Fingerprint: 8A17 B57C 6879 1863 DE55 8012 AB4D 6098 8826 6868
Andrew McNabb writes:
I've seen people use an awful workaround for this:
print('{spam} and {eggs}'.format(locals()))
While it looks a little magical, the proposed syntax would be an improvement (especially when there are many arguments):
print('{spam} and {eggs}'.format(=spam, =eggs))
You're proposing that the "awful" workaround be made magical, builtin, and available to be used in any situation whether appropriate or not? I'll take the explicit use of locals any time.
On Tue, Jun 25, 2013 at 02:50:27AM +0900, Stephen J. Turnbull wrote:
Andrew McNabb writes:
I've seen people use an awful workaround for this:
print('{spam} and {eggs}'.format(locals()))
While it looks a little magical, the proposed syntax would be an improvement (especially when there are many arguments):
print('{spam} and {eggs}'.format(=spam, =eggs))
You're proposing that the "awful" workaround be made magical, builtin, and available to be used in any situation whether appropriate or not?
No, I'm not. That would look like this:
print('{spam} and {eggs}'.format())
And it would be an extraordinarily bad idea. However, the OP proposed something else, so I'm not sure how relevant this is. I'm not even sure I like it, but many of the responses have denied the existence of the use case rather than criticizing the solution.
I'll take the explicit use of locals any time.
I don't think anyone likes the idea of magically passing locals into all function calls. -- Andrew McNabb http://www.mcnabbs.org/andrew/ PGP Fingerprint: 8A17 B57C 6879 1863 DE55 8012 AB4D 6098 8826 6868
Andrew McNabb writes:
You're proposing that the "awful" workaround be made magical, builtin, and available to be used in any situation whether appropriate or not?
No, I'm not. That would look like this:
print('{spam} and {eggs}'.format())
Ah, OK, that's right. Just goes to show that foo(=spam, =eggs) is really too confusing to be used. ;-)
I'll take the explicit use of locals any time.
I don't think anyone likes the idea of magically passing locals into all function calls.
My apologies, I didn't really think anybody wants "'{foo}'.format()" to DWIM. The intended comparison was to the proposed syntax, which I think is confusing and rather ugly.
On Mon, Jun 24, 2013 at 8:55 PM, Stephen J. Turnbull <stephen@xemacs.org>wrote:
Andrew McNabb writes:
You're proposing that the "awful" workaround be made magical, builtin, and available to be used in any situation whether appropriate or not?
No, I'm not. That would look like this:
print('{spam} and {eggs}'.format())
Ah, OK, that's right. Just goes to show that foo(=spam, =eggs) is really too confusing to be used. ;-)
I think you've just been reading all the mails in this thread of people claiming I eat children and worship satan :P
I'll take the explicit use of locals any time.
I don't think anyone likes the idea of magically passing locals into all function calls.
My apologies, I didn't really think anybody wants "'{foo}'.format()" to DWIM. The intended comparison was to the proposed syntax, which I think is confusing and rather ugly.
Yet obviously people DO do stuff like: _('{foo}') which walks the stack to find the locals and then puts them in there. I think this shows there is some room for a middle ground that might disincentivize people from going to those extremes :P Again, it's not about the exact syntax I suggested, it's about that middle ground.
On Jun 24, 2013, at 09:13 PM, Anders Hovmöller wrote:
Yet obviously people DO do stuff like:
_('{foo}')
which walks the stack to find the locals and then puts them in there. I think this shows there is some room for a middle ground that might disincentivize people from going to those extremes :P
Again, it's not about the exact syntax I suggested, it's about that middle ground.
One difference here is that the above magic is tucked inside a library. If you don't like magic (or you feel it's incomprehensible), don't use the library. A syntactic equivalent impacts the entire language. -Barry
On 25/06/13 03:58, Andrew McNabb wrote:
I'm not even sure I like it, but many of the responses have denied the existence of the use case rather than criticizing the solution.
I haven't seen anyone deny that it is possible to write code like spam(ham=ham, eggs=eggs, toast=toast) What I've seen is people deny that it happens *often enough* to deserve dedicated syntax to "fix" it. (I use scare quotes here because I don't actually think that repeating the name that way is a problem that needs fixing.) People have criticized the solution, for its lack of explicitness, for being focused on such a narrow special case, for its (subjective) ugliness, and for its fragility. Refactoring a variable name shouldn't require you to change the way you pass it to a function, but with this proposal, it does. If you refactor the name "ham" to "spam" in func(=ham, eggs=SCRAMBLED, toast=None, coffee='white') you also need to change the implicit keyword syntax back to ordinary explicit keyword syntax, or it will break. (Worse than breaking, if func happens to have a parameter "spam" as well, it will silently do the wrong thing.) That's a "feature smell", like a code smell but for features. I cannot think of any other feature, in any other language, where changing a variable's name requires you to change the syntax you can use on it. The OP seems to believe that the existence of an occasional function call with a spam=spam parameter is a major factor in discouraging the use of keyword arguments everywhere. I do not believe this is the case, but even if it were, I say, oh well. We shouldn't want keyword arguments *everywhere*, but only where they add clarity rather than mere verbosity. -- Steven
Steven D'Aprano wrote:
If you refactor the name "ham" to "spam" in
func(=ham, eggs=SCRAMBLED, toast=None, coffee='white')
you also need to change the implicit keyword syntax back to ordinary explicit keyword syntax, or it will break.
I don't see that as a major problem. If you change the name of a keyword argument, you have to review all the places it's used anyway.
I cannot think of any other feature, in any other language, where changing a variable's name requires you to change the syntax you can use on it.
That can happen already. If you're accepting a ** argument and passing it on, and the names change in such a way that the incoming and outgoing names no longer match, that whole strategy will stop working. The change required in that case is much bigger than just replacing '=foo' with 'foo=blarg'.
We shouldn't want keyword arguments *everywhere*, but only where they add clarity rather than mere verbosity.
I agree that wanting to using keyword arguments everywhere is excessive. But I do sympathise with the desire to improve DRY in this area. While the actual number of occasions I've encountered this sort of thing mightn't be very high, they stick in my mind as being particularly annoying. -- Greg
On Jun 24, 2013, at 17:40, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
I agree that wanting to using keyword arguments everywhere is excessive. But I do sympathise with the desire to improve DRY in this area. While the actual number of occasions I've encountered this sort of thing mightn't be very high, they stick in my mind as being particularly annoying.
Brandon Rhodes gave a nice presentation at Pycon this year on naming where he argued that "well-factored nouns" can help make code more readable... and enabling the above pattern would make it easier to do what he suggests sometimes. (For those that missed it: http://pyvideo.org/video/1676/the-naming-of-ducks-where-dynamic-types-meet-s..., starting about 08:20 through 11:00 or so, although I think the whole talk was great). I'm not saying that Python needs syntax for this, but I agree: the few times where I've had it come up, it is annoying. Jared
On Tue, Jun 25, 2013 at 12:40:34PM +1200, Greg Ewing wrote:
Steven D'Aprano wrote:
If you refactor the name "ham" to "spam" in
func(=ham, eggs=SCRAMBLED, toast=None, coffee='white')
you also need to change the implicit keyword syntax back to ordinary explicit keyword syntax, or it will break.
I don't see that as a major problem. If you change the name of a keyword argument, you have to review all the places it's used anyway.
I'm not talking about changing the name of the parameter, I'm talking about changing the name of the argument passed to the function. That is, the "ham" on the *right* of ham=ham, not the left. Of course, it takes far more faith in refactoring tools than I have to blindly run an automated tool over code changing names. But even if you review the code, it's very easy to miss that =ham was valid but after refactoring =spam is not. Whereas the explicit func(ham=ham) => func(ham=spam) continues to work perfectly. This proposed change leads to fragile code, where a trivial renaming will break function calls.
I cannot think of any other feature, in any other language, where changing a variable's name requires you to change the syntax you can use on it.
That can happen already. If you're accepting a ** argument and passing it on, and the names change in such a way that the incoming and outgoing names no longer match, that whole strategy will stop working. The change required in that case is much bigger than just replacing '=foo' with 'foo=blarg'.
But you don't have to change the *syntax*. func(a, b, c, **kwargs) keeps the same syntax, whether you refactor the name "kwargs" to "kw" or "extras" or any other name, or whatever you do to the keys inside it. I cannot think of any other syntax, certainly not in Python, which relies on a name being precisely one value rather than another in order to work. Wait, no, I have just thought of one: the magic treatment of super() inside methods in Python 3: def method(self, arg): super().method(arg) # works s = super; s().method(arg) # doesn't work But that's arguably because super() actually should be a keyword rather than just a regular builtin. In any case, I don't think that super() is a precedent for this proposal.
We shouldn't want keyword arguments *everywhere*, but only where they add clarity rather than mere verbosity.
I agree that wanting to using keyword arguments everywhere is excessive. But I do sympathise with the desire to improve DRY in this area. While the actual number of occasions I've encountered this sort of thing mightn't be very high, they stick in my mind as being particularly annoying.
I really wish people would stop referring to every trivially repeated token as "DRY". It is not. x = a + b + c Oh noes! Two plus signs! It's a DRY violation! Not. I exaggerate a little for effect, but it really does seem that many people think that any piece of code, no matter how trivially small, that is repeated is a DRY violation. But that's not what DRY is about. "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system." https://en.wikipedia.org/wiki/Don%27t_Repeat_Yourself which has nothing to do with writing spam=spam in a function call. It's no more of a DRY violation than "spam = -spam" would be. -- Steven
Steven D'Aprano wrote:
The change required in that case is much bigger than just replacing '=foo' with 'foo=blarg'.
But you don't have to change the *syntax*.
It's still a bigger change than any automated tool will be able to cope with, and if you're changing it manually, who cares if the syntax changes or not?
I cannot think of any other syntax, certainly not in Python, which relies on a name being precisely one value rather than another in order to work.
Pardon? *Every* time you use a name, you're relying on it being the same as at least one other name somewhere else.
I exaggerate a little for effect, but it really does seem that many people think that any piece of code, no matter how trivially small, that is repeated is a DRY violation. But that's not what DRY is about.
You seem to think I'm using a form of argument by authority: "It's DRY, therefore it's undesirable." But that's not what I'm saying. Rather, I'm saying that I feel it's undesirable, and I'm using the term DRY to *describe* what I think is undesirable about it. Maybe I'm not using the term quite the way it was originally meant, but that has no bearing on how I feel about the matter and my reasons for it.
"Every piece of knowledge must have a single, unambiguous, authoritative representation within a system."
https://en.wikipedia.org/wiki/Don%27t_Repeat_Yourself
which has nothing to do with writing spam=spam in a function call.
I'm not so sure about that. I've just been watching this: http://pyvideo.org/video/1676/the-naming-of-ducks-where-dynamic-types-meet-s... and going by the principles advocated there, if you change the name of the parameter in the called function, you *should* change the corresponding parameter of your function to match, for the sake of consistency. So '=spam' will continue to work fine after the change. It will also help you to keep your parameter names consistent by breaking if they're not! -- Greg
Greg Ewing writes:
I cannot think of any other syntax, certainly not in Python, which relies on a name being precisely one value rather than another in order to work.
Pardon? *Every* time you use a name, you're relying on it being the same as at least one other name somewhere else.
Ah, but here we have the case of two *different* names that are spelled the same[1], and what Steven is pointing out is that for this syntax to work, these different names that are spelled the same must stay in sync. If one's spelling changes, the other must change spelling, too. Footnotes: [1] That is, they are names in disjoint namespaces. Amusingly enough, there is a Stephen J. Turnbull who is a well-known expert on Japanese history, especially military history. Nice guy, no relation, never met in real space, so no confusion for us, no need for =Stephen syntax. But it confuses the heck out of ninja fanatics. ;-)
Stephen J. Turnbull wrote:
Ah, but here we have the case of two *different* names that are spelled the same[1], and what Steven is pointing out is that for this syntax to work, these different names that are spelled the same must stay in sync.
I dispute that they're different names. In the use cases I have in mind, it's no accident that the two names are spelled the same, because conceptually they represent the very same thing. Giving them different names would be confusing and probably indicate an error. If the names were only accidentally the same, I would probably want to rename one of them to avoid giving the misleading impression that they were related.
Amusingly enough, there is a Stephen J. Turnbull who is a well-known expert on Japanese history, especially military history. Nice guy, no relation, never met in real space, so no confusion for us, no need for =Stephen syntax. But it confuses the heck out of ninja fanatics. ;-)
That's quite a different situation -- these two Stephens really are different, and if they ever had to coexist in the same namespace, they would need to be given different names, e.g. StephenTheHistorian and StephenTheNinjaConfuser. And you would want to avoid assigning one to the other. -- Greg
From: Greg Ewing <greg.ewing@canterbury.ac.nz> Sent: Tuesday, June 25, 2013 1:15 AM
I dispute that they're different names. In the use cases I have in mind, it's no accident that the two names are spelled the same, because conceptually they represent the very same thing.
I've already asked Anders this, but let me ask you as well: What are the use cases you have in mind? As I see it, there are three cases (barring coincidences, which are obviously irrelevant) where this syntax could make a difference: 1. dict constructor 2. str.format 3. forwarding functions (like my example with get_appdir_url) #1 and #2 are definitely special cases. So, is it #3, or is there some broader use case here? And, if it is just #3, do you have the same argument that (I think) Anders has, or a different one? Again, let's try to use a realistic example instead of toy expressions that have no meaning no matter how they're written: def split(self, sep=None, maxsplit=-1): return self.__class__(self.wrapped_str.split(sep, maxsplit)) def split(self, *args, **kwargs): return self.__class__(self.wrapped_str.split(*args, **kwargs)) Anders' position seems to be that people _should_ be writing it as: def split(self, sep=None, maxsplit=-1): return self.__class__(self.wrapped_str.split(sep=sep, maxsplit=maxsplit)) … and the only reason we don't all write that is the repetition. I think he's wrong; there are perfectly good reasons to prefer the more common alternatives, so getting rid of the repetition would make very little difference. So, is this your case, and your argument? Sorry to belabor this, but I believe that if I'm missing the point, it's quite possible that Stephen and most of the other people against the idea are missing it in the same way.
That's quite a different situation -- these two Stephens really are different… And you would want to avoid assigning one to the other.
But you might want to pass one in place of the other. When Tokugawa Ieyasu said "Let your step be slow and steady, that you stumble not," was he talking about being cautious and moderate in changing the Python grammar file, or just about sieging castles? How will we know without an expert?
Andrew Barnert wrote:
I've already asked Anders this, but let me ask you as well: What are the use cases you have in mind?
As I see it, there are three cases ... 1. dict constructor 2. str.format 3. forwarding functions (like my example with get_appdir_url)
Mine are all 3. Here's a simplified version of something that crops up in various places in my GUI libraries. I have a widget, let's say a Button, and I want to calculate a default size for it during construction. To do that I need to know its font, which is one of the things that can potentially be passed as an argument to the constructor. So I want to "peek" at the font argument as it goes by: class Button(Widget): def __init__(self, text = "Hello", font = system_font, **kwds): default_size = calc_default_size(text, font) Widget.__init__(self, size = default_size, font = font, **kwds) (I should also point out that my Widget constructor accepts a very large set of potential keyword arguments (essentially any of its assignable properties) so passing them positionally is not an option.) Now, it's not so bad when there are only one or two passed-on arguments, but if you have a handful it starts to feel a bit wanky. I think I've figured out why this seemingly-small amount of redundancy bothers me so much. It's not just that I feel I'm saying the same thing twice, but that I feel I'm not saying *anything at all*. If I were to write this as an ordinary assignment: font = font then it would be completely redundant. Now I know full well that it's different with a keyword argument, because the two sides live in different namespaces. But some part of my brain still tries to tell me that I'm doing something silly -- to the extent that sometimes I'm tempted to deliberately give the two sides *different* names just so that it doesn't look so useless.
And, if it is just #3, do you have the same argument that (I think) Anders has, or a different one?
A different one. I'm not on any kind of crusade to push the use of keyword arguments for everything. -- Greg
From: Greg Ewing <greg.ewing@canterbury.ac.nz> Sent: Tuesday, June 25, 2013 3:09 PM
Andrew Barnert wrote:
What are the use cases you have in mind?
Here's a simplified version of something that crops up in various places in my GUI libraries. I have a widget, let's say a Button, and I want to calculate a default size for it during construction. To do that I need to know its font, which is one of the things that can potentially be passed as an argument to the constructor. So I want to "peek" at the font argument as it goes by:
class Button(Widget):
def __init__(self, text = "Hello", font = system_font, **kwds): default_size = calc_default_size(text, font) Widget.__init__(self, size = default_size, font = font, **kwds)
OK, I recognize this kind of thing. And I also recognize your uneasy feeling with it. Notice that in this case, you don't actually need to pull out font and pass it along: def __init__(self, text = "Hello", **kwds): default_size = calc_default_size(text, kwds.get('font', system_font)) Widget.__init__(self, size = default_size, **kwds) That also removes the need to copy the default value from the base class, and I think it gets across the idea of "peeking at the kwargs" more clearly than pulling one out into a positional-or-keyword arg. However, I'm not sure it's actually more readable this way. And it's certainly more verbose, not less, and all those get calls would add up if I were peeking at a lot of values. Also, that doesn't help if you want to do something with the value: def __init__(self, text = "Hello", font = system_font, **kwds): if os.sep in font: font = load_font_file(font) default_size = calc_default_size(text, font) Widget.__init__(self, size = default_size, font = font, **kwds)
Now, it's not so bad when there are only one or two passed-on arguments, but if you have a handful it starts to feel a bit wanky.
I think the issue isn't how many passed-on arguments you have, but how many you're peeking at. If you've got 20 arguments to pass along, but only need to peek at 1 or 2, your code is fine; it's when you've got 6 arguments to pass along and need to peek at all 6 that it gets nasty.
I think I've figured out why this seemingly-small amount of redundancy bothers me so much. It's not just that I feel I'm saying the same thing twice, but that I feel I'm not saying *anything at all*. If I were to write this as an ordinary assignment:
font = font
then it would be completely redundant. Now I know full well that it's different with a keyword argument, because the two sides live in different namespaces. But some part of my brain still tries to tell me that I'm doing something silly
Actually, I think it's a different problem for me. My brain initially wonders why I'm trying to use the default-value hack: filters = [lambda x, modulus=modulus: x % modulus == 0 for modulus in range(2, 6)] … on a function call instead of a function definition. Anyway, now that I see your example, I'm definitely more sympathetic to the problem. But I still don't like the proposed syntax, even if the problem were nearly compelling enough to add more syntax—which I don't think it is.
Andrew Barnert wrote:
Notice that in this case, you don't actually need to pull out font and pass it along:
def __init__(self, text = "Hello", **kwds): default_size = calc_default_size(text, kwds.get('font', system_font)) Widget.__init__(self, size = default_size, **kwds)
That's what I ended up doing in many cases. But using get() with a constant name has something of a nasty whiff about it as well.
I think the issue isn't how many passed-on arguments you have, but how many you're peeking at.
Yes, that's what I meant. -- Greg
On 06/25/2013 02:43 AM, Andrew Barnert wrote:
From: Greg Ewing:
I dispute that they're different names. In the use cases I have in mind, it's no accident that the two names are spelled the same, because conceptually they represent the very same thing.
I've already asked Anders this, but let me ask you as well: What are the use cases you have in mind?
As I see it, there are three cases (barring coincidences, which are obviously irrelevant) where this syntax could make a difference:
1. dict constructor 2. str.format 3. forwarding functions (like my example with get_appdir_url)
For me it's mostly #3, and the cases where I use it passing positionally is not an option because the arguments are keyword only. If something like this comes to pass, I would argue for the syntax that MRAB suggested: some_func(pos1, pos2, =, keyword, keyword, keyword) or possibly: some_func(pos1, pos2, *, keyword, keyword, keyword) to mirror the function definition. -- ~Ethan~
Greg Ewing writes:
Stephen J. Turnbull wrote:
Ah, but here we have the case of two *different* names that are spelled the same[1], and what Steven is pointing out is that for this syntax to work, these different names that are spelled the same must stay in sync.
I dispute that they're different names. In the use cases I have in mind,
Which are? If you've already explained them, or somebody else has, at least hint, please. I can abstractly imagine the kind of thing you're talking about, but I've never experienced a case where "foo(bar=bar)" bothered me because the identity of the object named was so strong as to offend my sensibility when writing the name twice.
it's no accident that the two names are spelled the same, because conceptually they represent the very same thing.
In such a case, I would almost certainly design the API (one or the other) to enable passing this name (that really lives in a super- namespace) as a positional argument. Or (as the OP posited, though you haven't) in a case where I have a bunch of such "no accident" variable names, I'd spend some time thinking about whether I should factor out a class here so I could pass a single "no accident" object name (preferably positionally). Bottom line, I still have trouble with the idea that this is a big enough problem to deserve a syntactic solution.
On 06/25/2013 03:15 AM, Greg Ewing wrote:
Stephen J. Turnbull wrote:
Ah, but here we have the case of two *different* names that are spelled the same[1], and what Steven is pointing out is that for this syntax to work, these different names that are spelled the same must stay in sync.
I dispute that they're different names. In the use cases I have in mind, it's no accident that the two names are spelled the same, because conceptually they represent the very same thing. Giving them different names would be confusing and probably indicate an error.
If the names were only accidentally the same, I would probably want to rename one of them to avoid giving the misleading impression that they were related.
I think some of the misunderstandings.. might be weather or not we are talking about function definitions or function calls, and/or other blocks that might be reused in different locations. If a function definitions were to set the calling syntax, then yes, it would be an issue because it would force the use of a particular name at all the call sites. But I don't think that is what is being proposed. But if 'name=' was just syntactic sugar for 'name=name', then it really wouldn't make any difference. Just use it at any call sight in place of a keyword argument pair that is alike. The compiler would just generate the same code as if you did use the full 'name=name' notation. And all the same rules would apply. But I'm still -1 It looks too much like a pass by reference to me, which python doesn't currently do. And I don't like the '=' with nothing on the right. The 'spam=spam' pair that is being discussed is binding an object bound to the spam on the right to a new name in a new location. I think the shorter syntax will make that harder to see and understand for new users. What the notation is really doing is.. future_local name = name I can't think of a good alternative syntax for that, that which is as clear as 'f(spam=spam)'. But I do find these discussions interesting because they can stimulate new ideas I otherwise wouldn't think of. Cheers, Ron
Ron Adam wrote:
And I don't like the '=' with nothing on the right.
Expanding on the suggestion someone made of having a single marker of some kind in the argument list, I came up with this: def __init__(self, text, font = system_font, style = 'plain'): default_size = calc_button_size(text, font, style) Widget.__init__(self, size = default_size, pass font, style) -- Greg
On 06/26/2013 12:48 AM, Greg Ewing wrote:
Ron Adam wrote:
And I don't like the '=' with nothing on the right.
Expanding on the suggestion someone made of having a single marker of some kind in the argument list, I came up with this:
def __init__(self, text, font = system_font, style = 'plain'): default_size = calc_button_size(text, font, style) Widget.__init__(self, size = default_size, pass font, style)
I don't care for it. A word doesn't stand out like a character does, plus this usage of pass is completely different from its normal usage. We're already used to interpreting '*' as a coin with two sides, let's stick with it: def apply_map(map, target, *, frobble): # '*' means frobble is keyword only ... and later: frobble = some_funny_stuff_here() . . . apply_map(map=kansas, target=toto, *, frobble) # '*' means frobble maps to keyword frobble -- ~Ethan~
On 26 June 2013 09:04, Ethan Furman <ethan@stoneleaf.us> wrote:
On 06/26/2013 12:48 AM, Greg Ewing wrote:
Ron Adam wrote:
And I don't like the '=' with nothing on the right.
Expanding on the suggestion someone made of having a single marker of some kind in the argument list, I came up with this:
def __init__(self, text, font = system_font, style = 'plain'): default_size = calc_button_size(text, font, style) Widget.__init__(self, size = default_size, pass font, style)
I don't care for it.
A word doesn't stand out like a character does, plus this usage of pass is completely different from its normal usage.
We're already used to interpreting '*' as a coin with two sides, let's stick with it:
def apply_map(map, target, *, frobble): # '*' means frobble is keyword only ...
and later:
frobble = some_funny_stuff_here() . . . apply_map(map=kansas, target=toto, *, frobble) # '*' means frobble maps to keyword frobble
Whilst Greg Ewing has made me also much more sympathetic to this view, I feel that: 1) This is nearly unreadable - it does not say what it does in the slightest 2) It's added syntax - that's a high barrier. I'm not convinced it's worth it yet. 3) It still feels like hackery; I might prefer something explicitly hackery like this: apply_map(map=kansas, target=toto, **locals("frobble")) where locals is: def locals(*args): if args: return {arg:original_locals()[arg] for arg in args} else: return original_locals() For Greg's he'd use: def __init__(self, text="Hello", font=system_font, **kwds): default_size = calc_default_size(text, font) Widget.__init__(self, size=default_size, **locals("font"), **kwds) or even def __init__(self, text = "Hello", font = system_font, **kwds): default_size = calc_default_size(text, font) Widget.__init__(self, **locals("size", "font"), **kwds) under the asumption that http://bugs.python.org/issue2292 does get implemented first. For reference, he is using (respaced for consistency): def __init__(self, text="Hello", font=system_font, **kwds): default_size = calc_default_size(text, font) Widget.__init__(self, size=default_size, font=font, **kwds) Note that this is only a way to suggest that *there might be another way*; maybe something involving objects. 3 cont.) The reason I think it feels like hackery is simply that I don't feel like Python is ever "reference by name"; objects don't know what they are called (some know what they *were* named; but they have no guarantee it's true) it feels *very wrong* to give "foobar" to function and have that function somehow extract the name! I know it's not doing that, but it's ever-so-close. However; maybe: class AttrDict(dict): def __init__(self, mapping, **defaults): super().__init__(defaults, **mapping) self.__dict__ = self def __init__(self, **kwds): kwds = AttrDict(kwds, text="Hello", font=system_font) kwds.size = calc_default_size(kwds.text, kwds,font) Widget.__init__(self, **kwds) and a decorator could even make this: @dynamic_kwds(text="Hello", font=system_font) def __init__(self, kwds): kwds.size = calc_default_size(kwds.text, kwds,font) Widget.__init__(self, **kwds) which would have the same run-time effect as the original (the second keeps re-evaluating the signature). Again; I mention these only to show people not to have a restricted idea on what the solution looks like - don't just try and fix the symptoms of the problem.
On 06/26/2013 09:46 AM, Joshua Landau wrote:
However; maybe:
class AttrDict(dict): def __init__(self, mapping, **defaults): super().__init__(defaults, **mapping) self.__dict__ = self
def __init__(self, **kwds): kwds = AttrDict(kwds, text="Hello", font=system_font)
kwds.size = calc_default_size(kwds.text, kwds,font)
Widget.__init__(self, **kwds)
and a decorator could even make this:
@dynamic_kwds(text="Hello", font=system_font) def __init__(self, kwds): kwds.size = calc_default_size(kwds.text, kwds,font) Widget.__init__(self, **kwds)
which would have the same run-time effect as the original (the second keeps re-evaluating the signature).
Again; I mention these only to show people not to have a restricted idea on what the solution looks like - don't just try and fix the symptoms of the problem.
It's always good advise to keep an open mind. I think the participants of this thread know they can use functions and decorators as helpers, but those always have some cost in processing time and memory usage. It's much harder to find a clean solution that doesn't use those. Cheers, Ron
On 06/26/2013 07:46 AM, Joshua Landau wrote:
On 26 June 2013 09:04, Ethan Furman <ethan@stoneleaf.us> wrote:
On 06/26/2013 12:48 AM, Greg Ewing wrote:
Ron Adam wrote:
And I don't like the '=' with nothing on the right.
Expanding on the suggestion someone made of having a single marker of some kind in the argument list, I came up with this:
def __init__(self, text, font = system_font, style = 'plain'): default_size = calc_button_size(text, font, style) Widget.__init__(self, size = default_size, pass font, style)
I don't care for it.
A word doesn't stand out like a character does, plus this usage of pass is completely different from its normal usage.
We're already used to interpreting '*' as a coin with two sides, let's stick with it:
def apply_map(map, target, *, frobble): # '*' means frobble is keyword only ...
and later:
frobble = some_funny_stuff_here() . . . apply_map(map=kansas, target=toto, *, frobble) # '*' means frobble maps to keyword frobble
Whilst Greg Ewing has made me also much more sympathetic to this view, I feel that:
1) This is nearly unreadable - it does not say what it does in the slightest
And the '*' and '**' in function defintions do?
2) It's added syntax - that's a high barrier. I'm not convinced it's worth it yet.
It is a high barrier; but this does add a bit of symmetry to the new '*'-meaning-keyword-only symbol.
3) It still feels like hackery; I might prefer something explicitly hackery like this:
You'll get used to it. ;) -- ~Ethan~
On 26 June 2013 16:24, Ethan Furman <ethan@stoneleaf.us> wrote:
On 06/26/2013 07:46 AM, Joshua Landau wrote:
On 26 June 2013 09:04, Ethan Furman <ethan@stoneleaf.us> wrote:
A word doesn't stand out like a character does, plus this usage of pass is completely different from its normal usage.
We're already used to interpreting '*' as a coin with two sides, let's stick with it:
def apply_map(map, target, *, frobble): # '*' means frobble is keyword only ...
and later:
frobble = some_funny_stuff_here() . . . apply_map(map=kansas, target=toto, *, frobble) # '*' means frobble maps to keyword frobble
Whilst Greg Ewing has made me also much more sympathetic to this view, I feel that:
1) This is nearly unreadable - it does not say what it does in the slightest
And the '*' and '**' in function defintions do?
Yes. The "*" symbol means "unpack" across a very large part of python, and the lone "*" was a simple extension to what it already did. There was no leap; I could've guessed what it did. It does not mean "magically make an object know what its name is and then unpack both of those -- implicitly over all of the following args!".
2) It's added syntax - that's a high barrier. I'm not convinced it's worth it yet.
It is a high barrier; but this does add a bit of symmetry to the new '*'-meaning-keyword-only symbol.
I don't think it does - there's no symmetry as they have completely different functions.
3) It still feels like hackery; I might prefer something explicitly hackery like this:
You'll get used to it. ;)
I bet you I won't :P.
On 06/26/2013 04:15 PM, Joshua Landau wrote:
On 26 June 2013 16:24, Ethan Furman <ethan@stoneleaf.us> wrote:
On 06/26/2013 07:46 AM, Joshua Landau wrote:
On 26 June 2013 09:04, Ethan Furman <ethan@stoneleaf.us> wrote:
A word doesn't stand out like a character does, plus this usage of pass is completely different from its normal usage.
We're already used to interpreting '*' as a coin with two sides, let's stick with it:
def apply_map(map, target, *, frobble): # '*' means frobble is keyword only ...
and later:
frobble = some_funny_stuff_here() . . . apply_map(map=kansas, target=toto, *, frobble) # '*' means frobble maps to keyword frobble
Whilst Greg Ewing has made me also much more sympathetic to this view, I feel that:
1) This is nearly unreadable - it does not say what it does in the slightest
And the '*' and '**' in function defintions do?
Yes. The "*" symbol means "unpack" across a very large part of python, and the lone "*" was a simple extension to what it already did. There was no leap; I could've guessed what it did. It does not mean "magically make an object know what its name is and then unpack both of those -- implicitly over all of the following args!".
Until recently the '*' meant 'pack' if it was in a function header, and 'unpack' if it was in a function call, and wasn't usable anywhere else. Now it also means 'unpack' in assignments, as well as 'keywords only after this spot' in function headers. Likewise with '**' (except for the assignments part). I don't know about you, but the first time I saw * and ** I had no idea what they did and had to learn it.
2) It's added syntax - that's a high barrier. I'm not convinced it's worth it yet.
It is a high barrier; but this does add a bit of symmetry to the new '*'-meaning-keyword-only symbol.
I don't think it does - there's no symmetry as they have completely different functions.
Like pack and unpack are completely different? ;) I see it as: function header: '*' means only keywords accepted after this point function call: '*' okay, here's my keywords ;)
3) It still feels like hackery; I might prefer something explicitly hackery like this:
You'll get used to it. ;)
I bet you I won't :P.
heheh -- ~Ethan~
On 06/26/2013 08:16 PM, Ethan Furman wrote:
I don't know about you, but the first time I saw * and ** I had no idea what they did and had to learn it.
One of my fist thoughts when I first learned Python was that it would have made things clearer if they used two different symbols for pack and unpack. def foo(*args, **kwds): return bar(^args, ^^kwds) There isn't any issue for the computer having those the same, but as a human, the visual difference would have helped make it easier to learn. But it's very hard to change something like this. And even harder to change how people think once they are used to something. Cheers, Ron
On 27/06/13 13:01, Ron Adam wrote:
On 06/26/2013 08:16 PM, Ethan Furman wrote:
I don't know about you, but the first time I saw * and ** I had no idea what they did and had to learn it.
One of my fist thoughts when I first learned Python was that it would have made things clearer if they used two different symbols for pack and unpack.
def foo(*args, **kwds): return bar(^args, ^^kwds)
There isn't any issue for the computer having those the same, but as a human, the visual difference would have helped make it easier to learn.
I don't think it would be easier. I think that means you just have to learn two things instead of one, and have the added source of errors when you get them mixed up: def foo(^args, ^^kwds): return bar(*args, **kwds) We don't seem to have problems with this: items[x] = items[y] rather than: items{x} = items[y] Or should it be the other way around? -- Steven
On 06/27/2013 07:44 AM, Steven D'Aprano wrote:
We don't seem to have problems with this:
items[x] = items[y]
rather than:
items{x} = items[y]
Or should it be the other way around?
Yes, that was my thoughts when I first started to see *args in different places. (>10 years ago). I'm mostly over it ;-) Cheers, Ron
On 27 June 2013 02:16, Ethan Furman <ethan@stoneleaf.us> wrote:
On 06/26/2013 04:15 PM, Joshua Landau wrote:
On 26 June 2013 16:24, Ethan Furman <ethan@stoneleaf.us> wrote:
On 06/26/2013 07:46 AM, Joshua Landau wrote:
I feel that:
1) This is nearly unreadable - it does not say what it does in the slightest
And the '*' and '**' in function defintions do?
Yes. The "*" symbol means "unpack" across a very large part of python, and the lone "*" was a simple extension to what it already did. There was no leap; I could've guessed what it did. It does not mean "magically make an object know what its name is and then unpack both of those -- implicitly over all of the following args!".
Until recently the '*' meant 'pack' if it was in a function header, and 'unpack' if it was in a function call, and wasn't usable anywhere else. Now it also means 'unpack' in assignments, as well as 'keywords only after this spot' in function headers.
True; but "unpack" and "pack" are synonymous operations; and as I said the "keywords-only" wasn't a new feature, really. It was just a *restricted* version of its previous usage; packing. It didn't actually add anything but a useful subset of what we already had. I say "pack" and "unpack" are synonymous in the same way I think that these are synonymous uses of "+": a + 5 = 10 # So a = 5 a = 5 + 10 # So a = 15 You do the "same thing" but on different sides of the equation, or function call. The definition you seem to want to add is completely different.
Likewise with '**' (except for the assignments part).
I don't know about you, but the first time I saw * and ** I had no idea what they did and had to learn it.
Yeah, true. *However*, and this is the point I'm trying to make: after I knew what def func(first_arg, *rest_of_args): pass did, I was able to guess (approximately) what: first_arg, *rest_of_args = iterable did. If you cannot, you probably need more trust in the developers. I would *never* have thought that: func(*, arg): pass meant func(arg=arg): pass despite that I sort of get the *very slight* (but not really even that) symmetry (now that you've explained it).
2) It's added syntax - that's a high barrier. I'm not convinced it's worth it yet.
It is a high barrier; but this does add a bit of symmetry to the new '*'-meaning-keyword-only symbol.
I don't think it does - there's no symmetry as they have completely different functions.
Like pack and unpack are completely different? ;)
No, they are symmetrical; like ◀ and ▶ are symmetrical. They are different, but the *same shape*.
I see it as:
function header: '*' means only keywords accepted after this point
function call: '*' okay, here's my keywords ;)
But you don't give keywords, they magically appear. My "gut reaction" would be that: foo(*, blah) causes an error ("only keywords allowed after *"). I know it's pointless thing to do, but it's what it looks like. I would then go and "make up" several things to make it seem non-useless like: foo(*, mapping1, mapping2) == foo(**mapping1, **mapping2) but again it makes no sense. I feel no real symmetry from this. Something like: foo(*=blah, *=foo) would make a lot more sense and actually be symetrical, but I agree it's not as clean (nor am I a fan of it; this was just an example). Please remember that: def foo(*, a) != def foo(*, a=b) despite that both are valid -- you are seeing, I think, a false symmetry. I don't think I've really made my point clear in this last section, but I tried.
On 27 June 2013 22:10, Joshua Landau <joshua.landau.ws@gmail.com> wrote:
I would *never* have thought that:
func(*, arg): pass
meant
func(arg=arg): pass
It doesn't mean that, since a bare keyword-only argument *must* be passed in when calling the function (as it has no default value).
despite that I sort of get the *very slight* (but not really even that) symmetry (now that you've explained it).
The "*" in the keyword-only parameter syntax still refers to positional argument packing. It just leaves the destination undefined to say "no extra positional arguments are allowed" rather than "any extra positional arguments go here". The parameters that appear after that entry are keyword only solely by a process of elimination - if all the positional arguments have been consumed (or disallowed), then any parameters that are left *have* to be passed as keywords, since positional and keyword are the only two argument passing options we offer. On the calling side, we've actually toyed with the idea of removing the argument ordering restrictions, because they imply a parallel with the parameter definitions that doesn't actually exist. We haven't though, as it's significant extra work for no compelling benefit. As far as this whole thread goes, though, we're still in the situation where the simplest mechanisms Python currently has to extract a submap from a mapping are a dict comprehension and operator.itemgetter. These proposed changes to function calling syntax then target a very niche case of that broader problem, where the mapping is specifically the current local variables and the destination for the submap is a function call with like named parameters. This is *not* the calibre of problem that prompts us to make Python harder to learn by adding new syntax. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On 27 June 2013 13:55, Nick Coghlan <ncoghlan@gmail.com> wrote:
On 27 June 2013 22:10, Joshua Landau <joshua.landau.ws@gmail.com> wrote:
I would *never* have thought that:
func(*, arg): pass
meant
func(arg=arg): pass
It doesn't mean that, since a bare keyword-only argument *must* be passed in when calling the function (as it has no default value).
despite that I sort of get the *very slight* (but not really even that) symmetry (now that you've explained it).
The "*" in the keyword-only parameter syntax still refers to positional argument packing. It just leaves the destination undefined to say "no extra positional arguments are allowed" rather than "any extra positional arguments go here".
The parameters that appear after that entry are keyword only solely by a process of elimination - if all the positional arguments have been consumed (or disallowed), then any parameters that are left *have* to be passed as keywords, since positional and keyword are the only two argument passing options we offer.
On the calling side, we've actually toyed with the idea of removing the argument ordering restrictions, because they imply a parallel with the parameter definitions that doesn't actually exist. We haven't though, as it's significant extra work for no compelling benefit.
I... don't understand. Did you perhaps misread my post? To reiterate, in shorthand, I am arguing against (I think -- if you are right then I've no idea what's actually happening).: foo(*, bar, spam) meaning foo(bar=bar, spam=spam) partially by pointing out that it isn't symmetrical with def foo(*, bar, spam): ... which means nothing of the sort. Of course, if I'm the one confused then I'm afraid that I just have no idea what I'm on about.
As far as this whole thread goes, though, we're still in the situation where the simplest mechanisms Python currently has to extract a submap from a mapping are a dict comprehension and operator.itemgetter.
Thank you for stating that root of the problem that I've had on the tip of my tongue. Perhaps: "dict & set" or even "dict & iter" would work, although it looks unwieldy, and I'm not sure I like it.
These proposed changes to function calling syntax then target a very niche case of that broader problem, where the mapping is specifically the current local variables and the destination for the submap is a function call with like named parameters. This is *not* the calibre of problem that prompts us to make Python harder to learn by adding new syntax.
On 27 June 2013 23:05, Joshua Landau <joshua.landau.ws@gmail.com> wrote:
I... don't understand. Did you perhaps misread my post?
Sorry, I should have been clearer - I was agreeing with you :) Ethan had indicated that he saw the use of a bare "*" to introduce keyword only argument parameter declarations as some magic new syntax unrelated to tuple packing. It isn't - it's still a tuple packing declaration like "*args", it's just one *without a named destination*, so we can put more stuff after it in the parameter list without allowing an arbitrary number of positional arguments. The other places where we allow tuple packing in a binding target are positional only, so there's no need for such a notation. It's certainly one of the more obscure aspects of Python's syntax, but there's still an underlying logic to it. (PEP 3102 has the details, but be aware that PEP uses the word "argument" in a few places where it should say "parameter" - PEP 362 better covers the differences, although a useful shorthand is "as part of a function call, parameter names are bound to argument values") Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On 27 June 2013 13:55, Nick Coghlan <ncoghlan@gmail.com> wrote:
As far as this whole thread goes, though, we're still in the situation where the simplest mechanisms Python currently has to extract a submap from a mapping are a dict comprehension and operator.itemgetter. These proposed changes to function calling syntax then target a very niche case of that broader problem, where the mapping is specifically the current local variables and the destination for the submap is a function call with like named parameters. This is *not* the calibre of problem that prompts us to make Python harder to learn by adding new syntax.
Sorry for the necromancy; but I just want to shine some inspiration I had recently on this: We *do* have a way of extracting a submap from a dict. We *already* have a way of writing foo = foo across scopes, as we want to do here. This problem *has* been solved before, and it looks like: from module import these, are, in, the, submap I haven't been able to find a really good syntax from this, but something like: 1) foo(a, b, **(key1, key2 from locals())) 2) {**(key1, key2 from locals())} 3) import key1, key2 from {"key1": 123, "key2": 345} etc. And, á mon avis, it's a hell of a lot better than previous proposals for (1) and (2) from this thread, and (3) from other threads. Again, not a proposal per se but a revelation.
On 30/06/13 02:20, Joshua Landau wrote:
We*do* have a way of extracting a submap from a dict.
Er, not really. Here's a dict: {} What you talk about is extracting named attributes from a module or package namespace. That modules happen to use a dict as the storage mechanism is neither here nor there. I can do this: class NoDict(object): __slots__ = ['eggs'] fake_module = NoDict() fake_module.eggs = 42 assert not hasattr(fake_module, '__dict__') sys.modules['spam'] = fake_module from spam import eggs and it all just works. But I'm no closer to extracting a subdict from a dict.
We*already* have a way of writing
foo = foo
across scopes, as we want to do here.
We sure do. And that's by just explicitly writing foo=foo. This is not a problem that needs solving. I'm kind of astonished that so many words have been spilled on a non-problem just to save a few characters for such a special case.
This problem*has* been solved before, and it looks like:
from module import these, are, in, the, submap
As I show above, this is about named attribute access, not dicts. And as you admit below, it doesn't lead to good syntax for extracting a subdict:
I haven't been able to find a really good syntax from this, but something like:
1) foo(a, b, **(key1, key2 from locals()))
2) {**(key1, key2 from locals())}
3) import key1, key2 from {"key1": 123, "key2": 345}
etc.
And, á mon avis, it's a hell of a lot better than previous proposals for (1) and (2) from this thread, and (3) from other threads.
Again, not a proposal per se but a revelation.
-- Steven
On 29/06/2013 17:56, Steven D'Aprano wrote:
On 30/06/13 02:20, Joshua Landau wrote:
We*do* have a way of extracting a submap from a dict.
Er, not really. Here's a dict: {}
What you talk about is extracting named attributes from a module or package namespace. That modules happen to use a dict as the storage mechanism is neither here nor there. I can do this:
class NoDict(object): __slots__ = ['eggs']
fake_module = NoDict() fake_module.eggs = 42 assert not hasattr(fake_module, '__dict__')
sys.modules['spam'] = fake_module
from spam import eggs
and it all just works. But I'm no closer to extracting a subdict from a dict.
We*already* have a way of writing
foo = foo
across scopes, as we want to do here.
We sure do. And that's by just explicitly writing foo=foo. This is not a problem that needs solving. I'm kind of astonished that so many words have been spilled on a non-problem just to save a few characters for such a special case.
This problem*has* been solved before, and it looks like:
from module import these, are, in, the, submap
As I show above, this is about named attribute access, not dicts. And as you admit below, it doesn't lead to good syntax for extracting a subdict:
I haven't been able to find a really good syntax from this, but something like:
1) foo(a, b, **(key1, key2 from locals()))
2) {**(key1, key2 from locals())}
3) import key1, key2 from {"key1": 123, "key2": 345}
etc.
You could just add a method to dict:
foo(a, b, **locals().subdict(["key1", "key2"]))
And, á mon avis, it's a hell of a lot better than previous proposals for (1) and (2) from this thread, and (3) from other threads.
Again, not a proposal per se but a revelation.
On 29 June 2013 18:19, MRAB <python@mrabarnett.plus.com> wrote:
On 30/06/13 02:20, Joshua Landau wrote:
I haven't been able to find a really good syntax from this, but something like:
1) foo(a, b, **(key1, key2 from locals()))
2) {**(key1, key2 from locals())}
3) import key1, key2 from {"key1": 123, "key2": 345}
etc.
You could just add a method to dict:
foo(a, b, **locals().subdict(["key1", "key2"]))
Only for (1), and (2) once http://bugs.python.org/issue2292 is in. Again, though, I don't really care about the syntax as it's not the point I was making.
On 29 June 2013 17:56, Steven D'Aprano <steve@pearwood.info> wrote:
On 30/06/13 02:20, Joshua Landau wrote:
We*do* have a way of extracting a submap from a dict.
Er, not really. Here's a dict: {}
What you talk about is extracting named attributes from a module or package namespace. That modules happen to use a dict as the storage mechanism is neither here nor there. I can do this:
class NoDict(object): __slots__ = ['eggs']
fake_module = NoDict() fake_module.eggs = 42 assert not hasattr(fake_module, '__dict__')
sys.modules['spam'] = fake_module
from spam import eggs
and it all just works. But I'm no closer to extracting a subdict from a dict.
I realise you used "slots", but it's still a dictionary. Well, OK, it's merely a "mapping" but I thought we duck-typed. So that's what you just did. You took a part of a namespace and added it to another namespace. That's what we wanted to do with: function_call(pass "arg" from local namespace to function's namespace) The fact that Python's namespaces are dictionaries isn't irrelevant. They're the same thing, and I think it's foolish to claim that's purely by implementation. Dictionaries are mappings are namespaces; they provide the *same* functionality through the same interface with slightly different semi-arbitrary restrictions and APIs. Just imagine my post but using "namespace" instead of "dictionary" and you might get the point.
We*already* have a way of writing
foo = foo
across scopes, as we want to do here.
We sure do. And that's by just explicitly writing foo=foo. This is not a problem that needs solving. I'm kind of astonished that so many words have been spilled on a non-problem just to save a few characters for such a special case.
As has been shown, this is actually a problem that quite a lot of people care about. There are a lot of threads (by far not just this one) that have understood that moving an argument from one namespace to another is something that feels like it should be able to look nicer. Surely you'd understand the problem if we had to write: module = import("module") arg = module.arg foo = module.foo blah = module.blah ... which, as I was saying, is *exactly the same problem* -- except that we've already solved it. That was what I'd realised and what I was pointing out. The rest of the post is along these lines, so which I've covered, and about the syntax, which is beside the point.
The *locals(...) thing looks pretty nice and would clearly take away a lot of the pain I was talking about! Thanks
On 26 Jun 2013, at 16:46, Joshua Landau <joshua.landau.ws@gmail.com> wrote:
On 26 June 2013 09:04, Ethan Furman <ethan@stoneleaf.us> wrote:
On 06/26/2013 12:48 AM, Greg Ewing wrote:
Ron Adam wrote:
And I don't like the '=' with nothing on the right.
Expanding on the suggestion someone made of having a single marker of some kind in the argument list, I came up with this:
def __init__(self, text, font = system_font, style = 'plain'): default_size = calc_button_size(text, font, style) Widget.__init__(self, size = default_size, pass font, style)
I don't care for it.
A word doesn't stand out like a character does, plus this usage of pass is completely different from its normal usage.
We're already used to interpreting '*' as a coin with two sides, let's stick with it:
def apply_map(map, target, *, frobble): # '*' means frobble is keyword only ...
and later:
frobble = some_funny_stuff_here() . . . apply_map(map=kansas, target=toto, *, frobble) # '*' means frobble maps to keyword frobble
Whilst Greg Ewing has made me also much more sympathetic to this view, I feel that:
1) This is nearly unreadable - it does not say what it does in the slightest
2) It's added syntax - that's a high barrier. I'm not convinced it's worth it yet.
3) It still feels like hackery; I might prefer something explicitly hackery like this:
apply_map(map=kansas, target=toto, **locals("frobble"))
where locals is:
def locals(*args): if args: return {arg:original_locals()[arg] for arg in args} else: return original_locals()
For Greg's he'd use:
def __init__(self, text="Hello", font=system_font, **kwds): default_size = calc_default_size(text, font) Widget.__init__(self, size=default_size, **locals("font"), **kwds)
or even
def __init__(self, text = "Hello", font = system_font, **kwds): default_size = calc_default_size(text, font) Widget.__init__(self, **locals("size", "font"), **kwds)
under the asumption that http://bugs.python.org/issue2292 does get implemented first.
For reference, he is using (respaced for consistency):
def __init__(self, text="Hello", font=system_font, **kwds): default_size = calc_default_size(text, font) Widget.__init__(self, size=default_size, font=font, **kwds)
Note that this is only a way to suggest that *there might be another way*; maybe something involving objects.
3 cont.) The reason I think it feels like hackery is simply that I don't feel like Python is ever "reference by name"; objects don't know what they are called (some know what they *were* named; but they have no guarantee it's true) it feels *very wrong* to give "foobar" to function and have that function somehow extract the name! I know it's not doing that, but it's ever-so-close.
However; maybe:
class AttrDict(dict): def __init__(self, mapping, **defaults): super().__init__(defaults, **mapping) self.__dict__ = self
def __init__(self, **kwds): kwds = AttrDict(kwds, text="Hello", font=system_font)
kwds.size = calc_default_size(kwds.text, kwds,font)
Widget.__init__(self, **kwds)
and a decorator could even make this:
@dynamic_kwds(text="Hello", font=system_font) def __init__(self, kwds): kwds.size = calc_default_size(kwds.text, kwds,font) Widget.__init__(self, **kwds)
which would have the same run-time effect as the original (the second keeps re-evaluating the signature).
Again; I mention these only to show people not to have a restricted idea on what the solution looks like - don't just try and fix the symptoms of the problem. _______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
On 06/26/2013 02:48 AM, Greg Ewing wrote:
Ron Adam wrote:
And I don't like the '=' with nothing on the right.
Expanding on the suggestion someone made of having a single marker of some kind in the argument list, I came up with this:
def __init__(self, text, font = system_font, style = 'plain'): default_size = calc_button_size(text, font, style) Widget.__init__(self, size = default_size, pass font, style)
Using the '*' in the call sites as suggested earlier because of it's consistency with function definitions is probably a better choice. In the above example, would the '*' (or pass) be in the same place as the it is in the function definition? Widget.__init__(self, *, size=default_size, font, style) Or: Widget.__init__(self, size=default_size, *, font, style) I think it should, because then there would be a clearer separation between what are positional and keyword arguments throughout a program. _Ron
On 06/26/2013 07:59 AM, Ron Adam wrote:
On 06/26/2013 02:48 AM, Greg Ewing wrote:
Ron Adam wrote:
And I don't like the '=' with nothing on the right.
Expanding on the suggestion someone made of having a single marker of some kind in the argument list, I came up with this:
def __init__(self, text, font = system_font, style = 'plain'): default_size = calc_button_size(text, font, style) Widget.__init__(self, size = default_size, pass font, style)
Using the '*' in the call sites as suggested earlier because of it's consistency with function definitions is probably a better choice.
In the above example, would the '*' (or pass) be in the same place as the it is in the function definition?
Widget.__init__(self, *, size=default_size, font, style)
Or:
Widget.__init__(self, size=default_size, *, font, style)
I think it should, because then there would be a clearer separation between what are positional and keyword arguments throughout a program.
Not at all. Just like we can use keyword arguments even when the '*'-keyword-only symbol has not been used, we should be able to use the '*'-variable-name-is-keyword whenever we have keyword arguments available -- which is most of the time. -- ~Ethan~
On Tue, Jun 25, 2013 at 06:47:57PM +1200, Greg Ewing wrote:
Steven D'Aprano wrote:
The change required in that case is much bigger than just replacing '=foo' with 'foo=blarg'.
But you don't have to change the *syntax*.
It's still a bigger change than any automated tool will be able to cope with, and if you're changing it manually, who cares if the syntax changes or not?
Because having to change the syntax is *one more thing* to worry about when you change a variable name, or change a parameter. Because forgetting to change the syntax is *one more thing* to go wrong. This makes this proposed feature *more* fragile rather than less, and I don't think we should be adding syntax that encourages fragile code for marginal benefit, particularly when the proposal goes against two of the Zen and, frankly, has nothing much going for it except a little less typing in a fraction of all function calls. It doesn't make code more readable, except in the sense that there's less to read. If anything, it's *less* readable because it looks like you've forgotten the parameter name: func(a, b, spam=None, ham="green", =eggs, extra=False, another=42*x) It's less explicit. It priviledges a special case that really isn't that important. Out of the infinite number of possible combinations of parameter=expression, what's so special about the one where the expression happens to be precisely the same string as the parameter? Even if it is special, it's not special *enough* to justify special syntax.
I cannot think of any other syntax, certainly not in Python, which relies on a name being precisely one value rather than another in order to work.
Pardon? *Every* time you use a name, you're relying on it being the same as at least one other name somewhere else.
Either I'm not explaining myself, or you're not understanding me. Let me give you some examples. "x = 2*y", if I rename y => z the statement remains "x = 2*z", I don't have to change it to "x = x.__mul__(z)". "mylist.append(None)", if I rename mylist => alist the statement remains "alist.append(None), I don't have to change it to "alist.extend([None])". "if seq:", if I rename seq => items the statement remains "if items:", I don't have to change it to "if bool(items):". BUT the proposed syntax is different from everything else in Python: "func(=spam)", but if I rename spam => ham, I must also change the way I call the function to "func(spam=ham)". The point isn't that it's hard to make that edit. The point is that I shouldn't need to make that edit.
I exaggerate a little for effect, but it really does seem that many people think that any piece of code, no matter how trivially small, that is repeated is a DRY violation. But that's not what DRY is about.
You seem to think I'm using a form of argument by authority: "It's DRY, therefore it's undesirable." But that's not what I'm saying. Rather, I'm saying that I feel it's undesirable, and I'm using the term DRY to *describe* what I think is undesirable about it. Maybe I'm not using the term quite the way it was originally meant, but that has no bearing on how I feel about the matter and my reasons for it.
This isn't a question of "original meaning", you are misusing DRY to describe something which has nothing to do with the principles behind DRY, also known as "Single Point of Truth". You might as well describe your dislike of func(spam=spam) as "a violation of the Liskov Substitution Principle", then say "well that's not what Barbara Liskov meant by the LSP, but it's what I mean". I'm sorry if it annoys you to be told that your understanding of Don't Repeat Yourself is mistaken, but it is. It is not "any repetition of code is a bad thing".
"Every piece of knowledge must have a single, unambiguous, authoritative representation within a system."
https://en.wikipedia.org/wiki/Don%27t_Repeat_Yourself
which has nothing to do with writing spam=spam in a function call.
I'm not so sure about that. I've just been watching this:
http://pyvideo.org/video/1676/the-naming-of-ducks-where-dynamic-types-meet-s...
and going by the principles advocated there, if you change the name of the parameter in the called function, you *should* change the corresponding parameter of your function to match, for the sake of consistency.
The called function may be called from a thousand places. Do you really think that because I decide I don't like my local variable to be called (say) "first_name", and want to call it "personal_name" instead, that I "should" force every other caller to change their local variable too, "for the sake of consistency"? I'm sure you know the proverb about foolish consistency. -- Steven
Because having to change the syntax is *one more thing* to worry about when you change a variable name, or change a parameter. Because forgetting to change the syntax is *one more thing* to go wrong. This makes this proposed feature *more* fragile rather than less, and I don't think we should be adding syntax that encourages fragile code for marginal benefit, particularly when the proposal goes against two of the Zen and, frankly, has nothing much going for it except a little less typing in a fraction of all function calls.
I agree with everything except the part of the last sentence after the comma where you misrepresent the entire issue at hand. Again.
It doesn't make code more readable, except in the sense that there's less to read. If anything, it's *less* readable because it looks like you've forgotten the parameter name:
Again you're conflating my suggested syntax with the underlying idea. The syntax could be "$foo" instead of "=foo" for example. This has the advantage that it'd be the same in a dict: "{$foo}", but it has the disadvantage of not being similar to the existing syntax. Again, the idea itself is a separate thing from the syntax I spent literally 2 seconds to come up with :P
It's less explicit.
You mean "more". It's absolutely more explicit than just using the position.
It priviledges a special case that really isn't that important. Out of the infinite number of possible combinations of parameter=expression, what's so special about the one where the expression happens to be precisely the same string as the parameter?
It's so special because one can be derived from the other. Which is impossible in all the other cases.
Even if it is special, it's not special *enough* to justify special syntax.
In your opinion. In my opinion we could have an open ended brain storm about these types of things, and we can acknowledge that it's a matter of trade offs. Either I'm not explaining myself, or you're not understanding me. Let me
give you some examples.
He was using your hyperbole against you :P
The point isn't that it's hard to make that edit. The point is that I shouldn't need to make that edit.
If you use the alternative of **locals() suggested as an alternative you're in tons and tons more trouble than just having to make that edit. Or even worse with the _() magic that goes through the stack and digs out the locals. *shudder* I understand you don't like my suggestion, but I hope we can both agree that locals() for anything but a string formatter with a literal, or rummaging through the stack is much much worse?
I'm sorry if it annoys you to be told that your understanding of Don't Repeat Yourself is mistaken, but it is. It is not "any repetition of code is a bad thing".
Words are defined not by dictionaries but by how they are used. Words change meaning over time.
and going by the principles advocated there, if you change the name of the parameter in the called function, you *should* change the corresponding parameter of your function to match, for the sake of consistency.
The called function may be called from a thousand places. Do you really think that because I decide I don't like my local variable to be called (say) "first_name", and want to call it "personal_name" instead, that I "should" force every other caller to change their local variable too, "for the sake of consistency"?
Again with the straw man. That wasn't what he said. He said that if 'personal_name' is *a better name for what it actually is* then it is a good idea to change it in the entire code base. It's always a good idea to make the names of stuff in your code better. This has nothing to do with this thread though!
By the way Anders, you're dropping attribution of the person you are quoting. That's a little impolite and makes it hard to know who said what when. On Tue, Jun 25, 2013 at 10:58:45AM +0200, Anders Hovmöller wrote:
Because having to change the syntax is *one more thing* to worry about when you change a variable name, or change a parameter. Because forgetting to change the syntax is *one more thing* to go wrong. This makes this proposed feature *more* fragile rather than less, and I don't think we should be adding syntax that encourages fragile code for marginal benefit, particularly when the proposal goes against two of the Zen and, frankly, has nothing much going for it except a little less typing in a fraction of all function calls.
I agree with everything except the part of the last sentence after the comma where you misrepresent the entire issue at hand. Again.
I'm not misrepresenting anything. I'm making a statement that, in my judgement, your proposal for implicit parameter names has no advantage except that you sometimes get to type less. You might not accept my judgement on this, but it is an honest one.
It doesn't make code more readable, except in the sense that there's less to read. If anything, it's *less* readable because it looks like you've forgotten the parameter name:
Again you're conflating my suggested syntax with the underlying idea. The syntax could be "$foo" instead of "=foo" for example.
I don't think I should be judged badly for responding to your actual proposal instead of the infinite number of things you might have proposed but didn't. [...]
It's less explicit.
You mean "more". It's absolutely more explicit than just using the position.
I'm not comparing it to positional arguments. I'm comparing: func(spam=spam) # parameter name is explicitly given func(=spam) # parameter name is implicit
It priviledges a special case that really isn't that important. Out of the infinite number of possible combinations of parameter=expression, what's so special about the one where the expression happens to be precisely the same string as the parameter?
It's so special because one can be derived from the other. Which is impossible in all the other cases.
That's incorrect. We could run wild with implied parameters, if we so cared: funct(=3*spam, =eggs+1, =cheese[0]) could be expanded to funct(spam=3*spam, eggs=eggs+1, cheese=cheese[0]) according to the rule, "if an an argument consists of an expression with exactly one name, then the parameter name is derived from that name". I'm not suggesting this as a good idea. I think it is a terrible idea. But it is conceivable that some language might derive parameter names from expressions that are more complex than a single name I believe that it's a bad idea even if the parameter is derived from a single name.
The point isn't that it's hard to make that edit. The point is that I shouldn't need to make that edit.
If you use the alternative of **locals() suggested as an alternative you're in tons and tons more trouble than just having to make that edit.
Why would I do that? Unless I knew the called function would silently ignore unrecognised keyword args, I wouldn't use the **locals() trick. And even if I did, I probably wouldn't use the trick anyway. -- Steven
I cannot think of any other feature, in any other language, where changing a variable's name requires you to change the syntax you can use on it.
Let me give you an example then! OCaml has exactly the feature I propose. It looks like this: foo ~bar And to be clear is the same as the python foo(bar=bar)
On 24 June 2013 19:41, Steven D'Aprano <steve@pearwood.info> wrote:
On 25/06/13 03:58, Andrew McNabb wrote:
I'm not even sure I like it, but many of the responses have denied the existence of the use case rather than criticizing the solution.
I haven't seen anyone deny that it is possible to write code like
spam(ham=ham, eggs=eggs, toast=toast)
What I've seen is people deny that it happens *often enough* to deserve dedicated syntax to "fix" it. (I use scare quotes here because I don't actually think that repeating the name that way is a problem that needs fixing.)
Sorry for being silent for agreeing with the original proposal - but indeed - I think it does happen often enough to ask for some change - doubly so with stgin/template formatting calls (It is not fun to have to maintain 3rd party code doing format(**locals() ) , and just writting every used constant twice - and _then_ having to justify to others why that, awfull as it looks, is way better than using the **locals() call. (and yes, it _does_ happen) , I think that there could possibly be something better than the proposed syntax -and we culd get to it - but as it is, it would eb good enough for me. js -><-
On 26 June 2013 16:43, Joao S. O. Bueno <jsbueno@python.org.br> wrote:
On 24 June 2013 19:41, Steven D'Aprano <steve@pearwood.info> wrote:
On 25/06/13 03:58, Andrew McNabb wrote:
I'm not even sure I like it, but many of the responses have denied the existence of the use case rather than criticizing the solution.
I haven't seen anyone deny that it is possible to write code like
spam(ham=ham, eggs=eggs, toast=toast)
What I've seen is people deny that it happens *often enough* to deserve dedicated syntax to "fix" it. (I use scare quotes here because I don't actually think that repeating the name that way is a problem that needs fixing.)
Sorry for being silent for agreeing with the original proposal - but indeed - I think it does happen often enough to ask for some change - doubly so with stgin/template formatting calls (It is not fun to have to maintain 3rd party code doing format(**locals() ) , and just writting every used constant twice - and _then_ having to justify to others why that, awfull as it looks, is way better than using the **locals() call. (and yes, it _does_ happen)
Is it really that awful? I mean, assuming you're sane and use .format_map. But, as was said before, most of the time it doesn't make much sense to do .format_map(locals()) as the variable you want should be part of some more-naturally accessed namespace.
, I think that there could possibly be something better than the proposed syntax -and we culd get to it - but as it is, it would eb good enough for me.
I'll take the explicit use of locals any time. Meaning you prefer "foo(bar=bar)" over "foo(**locals())" right? Because that seems to be the suggested solution here and I think that's pretty bad :P My suggestion isn't about introducing more magic, just a little bit of convenience for two common use cases: passing along variables with the same name to another function and throwing a bunch of variables into a dict (which is basically the same thing since "dict(foo=foo)" == "{'foo': foo}").
Anders Hovmöller writes:
I'll take the explicit use of locals any time.
I meant "locals" the function. I should have written "locals()".
My suggestion isn't about introducing more magic, just a little bit of convenience
It's syntax, which is always magic, or, if you prefer, "has arbitrary semantics which must be memorized". As syntax, it's ugly (IMO) and nonintuitive (by the rationale that "=" is an infix binary relation or assignment operator, not a unary prefix operator). Not to forget redundant, given the existence of locals() and positional arguments.
for two common use cases: passing along variables with the same name to another function
If the two functions were designed with similar signatures, using positional arguments is the obvious way to indicate this, at a saving of one "=" per argument. If they weren't and you don't feel like looking up the signature of the function you're calling (or are worried that the signature might change in a future version), **locals() wins at the expense of bringing in (possibly) a bunch of junk you don't want. But it's way shorter than a sequence of even *three* =-prefixed variable names (unless they're "x", "y", and "z"). I don't see a win here big enough to justify syntax, let alone *this* syntax.
and throwing a bunch of variables into a dict (which is basically the same thing since "dict(foo=foo)" == "{'foo': foo}").
This is a little more plausible, since you can't pun on the names of the parameters when using positional arguments, since dicts don't have positional arguments. Still, adding syntax is a high hurdle.
On 6/24/2013 12:23 PM, Andrew McNabb wrote:
Another use case where this would come in handy is with string formatting:
print('{spam} and {eggs}'.format(spam=spam, eggs=eggs))
I've seen people use an awful workaround for this:
print('{spam} and {eggs}'.format(locals()))
That should be print('{spam} and {eggs}'.format(**locals())) Why do you see an intended usage as an 'awful workaround'? If it is the inefficiency of unpacking and repacking a dict, that could be fixed. One possibility is for ** to just pass the mapping when the function only reads it, as is the case with .format (but how to know?). A direct solution for .format is to add a keyword-only mapping parameter: print('{spam} and {eggs}'.format(map=locals())) or mapping= names= or reps= (replacements) or strs= (strings) or dic= or ???=.
While it looks a little magical, the proposed syntax would be an improvement (especially when there are many arguments):
print('{spam} and {eggs}'.format(=spam, =eggs))
It looks pretty hideous to me ;-). And it still has repetition ;-). -- Terry Jan Reedy
On 24 June 2013 19:03, Terry Reedy <tjreedy@udel.edu> wrote:
On 6/24/2013 12:23 PM, Andrew McNabb wrote:
Another use case where this would come in handy is with string formatting:
print('{spam} and {eggs}'.format(spam=spam, eggs=eggs))
I've seen people use an awful workaround for this:
print('{spam} and {eggs}'.format(locals()))
That should be print('{spam} and {eggs}'.format(**locals()))
Why do you see an intended usage as an 'awful workaround'?
If it is the inefficiency of unpacking and repacking a dict, that could be fixed. One possibility is for ** to just pass the mapping when the function only reads it, as is the case with .format (but how to know?). A direct solution for .format is to add a keyword-only mapping parameter:
print('{spam} and {eggs}'.format(map=locals()))
or mapping= names= or reps= (replacements) or strs= (strings) or dic= or ???=.
Oh look! It's Guido's time machine! "Look, it's {what_you_wanted}!".format_map(locals()) Note that your suggestion would disallow: "{mapping}".format(mapping="HA")
While it looks a little magical, the proposed syntax would be an improvement (especially when there are many arguments):
print('{spam} and {eggs}'.format(=spam, =eggs))
It looks pretty hideous to me ;-). And it still has repetition ;-).
On 6/24/2013 2:03 PM, Terry Reedy wrote:
On 6/24/2013 12:23 PM, Andrew McNabb wrote:
Another use case where this would come in handy is with string formatting:
print('{spam} and {eggs}'.format(spam=spam, eggs=eggs))
I've seen people use an awful workaround for this:
print('{spam} and {eggs}'.format(locals()))
That should be print('{spam} and {eggs}'.format(**locals()))
or: print('{spam} and {eggs}'.format_map(locals())) which solves the inefficiency problem mentioned below.
Why do you see an intended usage as an 'awful workaround'?
If it is the inefficiency of unpacking and repacking a dict, that could be fixed. One possibility is for ** to just pass the mapping when the function only reads it, as is the case with .format (but how to know?). A direct solution for .format is to add a keyword-only mapping parameter:
print('{spam} and {eggs}'.format(map=locals()))
Today that would be: print('{map.spam} and {map.eggs}'.format(map=locals())) -- Eric.
On Mon, Jun 24, 2013 at 02:03:58PM -0400, Terry Reedy wrote:
print('{spam} and {eggs}'.format(locals()))
That should be print('{spam} and {eggs}'.format(**locals()))
Yes, you're right.
Why do you see an intended usage as an 'awful workaround'?
Mainly because I think it's magical and ugly and reduces readability. It's subjective, but I always use the also-ugly but less magical:
print('{spam} and {eggs}'.format(spam=spam, eggs=eggs))
print('{spam} and {eggs}'.format(=spam, =eggs))
It looks pretty hideous to me ;-).
I agree. :)
And it still has repetition ;-).
True, though it can be helpful to look at the function call and actually see what arguments are being passed in. Anyway, I do have plenty of code that suffers from function calls with f(x=x,y=y,z=z). Most of it is of the form that Greg Ewing pointed out, where one function passes arguments along to another function. I'm not sure if this is the right solution, and I agree that it's ugly, but there are use cases where it might help. I'm still probably -0 on it. -- Andrew McNabb http://www.mcnabbs.org/andrew/ PGP Fingerprint: 8A17 B57C 6879 1863 DE55 8012 AB4D 6098 8826 6868
On 06/24/2013 01:03 PM, Terry Reedy wrote:
While it looks a little magical, the proposed syntax would be an improvement (especially when there are many arguments):
print('{spam} and {eggs}'.format(=spam, =eggs))
It looks pretty hideous to me ;-). And it still has repetition ;-).
In larger programs, data like this would probably be in a dictionary or some other structure. There may be a large dictionary of many foods. And we would just use .format_map and not have any repetition where those are used. print('{spam} and {eggs}'.format_map(foods)) I think it would also be useful to get a sub_view of a dictionary. breakfast = ['spam', 'eggs'] print('{spam} and {eggs}'.format_map(foods.sub_view(breakfast))) The food dictionary might also have lunch items and dinner items in it, and we don't want to serve dinner for breakfast by mistake. ;-) Cheers, Ron
On 23 Jun 2013 18:23, "Anders Hovmöller" <boxed@killingar.net> wrote:
You initially said that ObjC deals with this problem better than Python,
I don't understand why you're making this argument in support of a
and now you say that it's better because it forces you to use the keyword names (actually they're part of the method name, but let's ignore that) _always_, which Python only forces you to do it when not using them positionally. proposal that would make Python even less explicit about keyword names, less like ObjC, and, by your analysis, harder to maintain and therefore worse.
I think you and I are talking about different things when talking about
"this problem". For me the problem is to avoid stuff like "foo(1, 'foo', None, 9, 'baz')", not avoid repeating names. I just believe that python has syntax that promotes positional arguments even when it makes the code worse. My suggestion might on the surface look like just a way to type less, but that misses the point. It's about shifting the balance towards keyword arguments. Then use Python 3 and declare your functions with keyword-only arguments. That means your APIs can no longer be invoked with positional arguments. You can do the same in Python 2 by accepting arbitrary kwargs and unpacking them with an inner function or retrieving them directly from the dictionary. We do this ourselves in the standard library for APIs where we expect it to significantly improve clarity at call sites (consider the "key" and "reverse" arguments to sorted and list.sort). Cheers, Nick.
_______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
On 22 June 2013 22:01, Anders Hovmöller <boxed@killingar.net> wrote:
Yes, but consistency, y'know. Why "bar = bar ≡ = bar" here and "bar = bar !≡ = bar" there?
Keyword arguments aren't the same thing as assignment. The consistency ship has sailed and we're not on it :P
Fair 'nuf, except that you wanted {:this, :ugly, :not, :a, :set, :but, :looks, :like, :one} too. Why "we need to be consistent here" but "we don't here, btw"?
I've skimmed a bit but I'm still unsure; why? How does Objective-C deal with this?
They deal with it with the nuclear option: all methods are 100% of the time ordered AND named. Unfortunately this has the side effect of a lot of method calls like "[foo setX:x y:y width:width height:height font:font]" which is almost exactly the same as the worst case in python: "foo.setStuff(x=x, y=y, width=width, height=height, font=font)".
This rather brutish approach forces a rather verbose style of writing, which is annoying when you just want to bang out a prototype but extremely valuable when having to maintain large code bases down the line. One of the main points about my suggestion is that there should be almost no overhead to using keyword arguments when just passing arguments along or when variable names are nice and descriptive. This would create a lower barrier to use, which in turn leads to more solid and more readable code (I hope!).
As other people have pointed out, you've sort'a just contradicted yourself.
There are so many more cases to cover and this doesn't fill them,
Like what? At least name one so we can have a discussion about it!
One? Well, the one above!
I agree that classes seem a bit far-fetched (personally I dislike that syntax) but what about:
def function(arg=arg): ...
def function(arg): self.arg = arg
(Where is self defined?)
Yeah, you know what I meant.
thing = dictionary[thing]
and so on, which are all of the same form, albeit with different "surroundings". We can't just double the whole syntax of Python for this!
I'm not suggesting that though. It seems to me like you're taking my suggestion in absurdum.
Hang on; you asked me to point out cases this *didn't* cover. I'd be very surprised if you managed to both not-propose and propose any of these.
Does foo(=bar) not bug you? Really?
Compared to foo(bar=bar)? No.
We'll just have to disagree. Strongly.
On 23 June 2013 06:07, Joshua Landau <joshua.landau.ws@gmail.com> wrote:
On 22 June 2013 20:51, Anders Hovmöller <boxed@killingar.net> wrote:
Ok. Why? I'm not saying you're wrong, I just want to know what the reasons are so I can understand why I was mistaken so I can forget about this idea :P
Does foo(=bar) not bug you? Really?
Indeed, any kind of "implied LHS" notation isn't going to happen in the foreseeable future for Python. That said, the repetitiveness of passing several local variables as keyword arguments *is* mildly annoying. A potential more fruitful avenue to explore may be to find a clean notation for extracting a subset of keys (along with their values) into a dictionary. With current syntax, the original example can already be written as something like this:
def submap(original, *names): ... return type(original)((name, original[name]) for name in names) ... a, b, c = 1, 2, 3 submap(locals(), "a", "b", "c") {'a': 1, 'b': 2, 'c': 3} f(**submap(locals(), "a", "b", "c")) {'a': 1, 'b': 2, 'c': 3} from collections import OrderedDict o = OrderedDict.fromkeys((1, 2, 3, 4, 5)) o OrderedDict([(1, None), (2, None), (3, None), (4, None), (5, None)]) submap(o, 1, 3, 5) OrderedDict([(1, None), (3, None), (5, None)])
(There are also plenty of possible variants on that idea, including making it a class method of the container, rather than deriving the output type from the input type) This is potentially worth pursuing, as Python currently only supports "operator.itemgetter" and comprehensions/generator expressions as a mechanism for retrieving multiple items from a container in a single expression. A helper function in collections, or a new classmethod on collections.Mapping aren't outside the realm of possibility. For the question of "How do I enlist the compiler's help in ensuring a string is an identifier?", you can actually already do that with a simple helper class:
class Identifiers: ... def __getattr__(self, attr): ... return attr ... ident = Identifiers() ident.a 'a' ident.b 'b' ident.c 'c'
Combining the two lets you write things like:
submap(locals(), ident.a, ident.b, ident.c) {'a': 1, 'b': 2, 'c': 3}
Personally, I'd like to see more exploration of what the language *already supports* in this area, before we start talking about adding dedicated syntax. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On 06/22/2013 08:26 PM, Nick Coghlan wrote:
This is potentially worth pursuing, as Python currently only supports "operator.itemgetter" and comprehensions/generator expressions as a mechanism for retrieving multiple items from a container in a single expression. A helper function in collections, or a new classmethod on collections.Mapping aren't outside the realm of possibility.
For the question of "How do I enlist the compiler's help in ensuring a string is an identifier?", you can actually already do that with a simple helper class:
>class Identifiers: ... def __getattr__(self, attr): ... return attr ... >ident = Identifiers() >ident.a 'a' >ident.b 'b' >ident.c 'c'
Combining the two lets you write things like:
>submap(locals(), ident.a, ident.b, ident.c) {'a': 1, 'b': 2, 'c': 3}
That's interesting. I noticed there isn't a way to easily split a dictionary with a list of keys. Other than using a loop. Any syntax that reduces name=name to a single name would look to me like a pass by reference notation. I don't think that would be good. (?) What I see in examples like above is the amount of additional work the CPU needs to do. I think that is more of an issue than name=name.
Personally, I'd like to see more exploration of what the language *already supports* in this area, before we start talking about adding dedicated syntax.
It may be easier to experiment with stuff like this if we could make the following possible. (*Not taking into account some things, like closures, etc... to keep the idea clear.) Wnere. result = f(...) # normal function call is the same as: args, kwds = func_parse_signature(f, ...) name_space = func_make_name_space(f, args, kwds) result = func_call_with_args(f, args, kwds) result = func_call_code(f, name_space) These functions create a way to reuse a functions parts in new ways. But it doesn't go the full step of being able to take functions apart and reassemble them. That's harder to do and keep everything working. Some of this is currently doable, but it involved hacking a function object/type or using exec. For example, the signature could be parsed by a decorator, with added features. Then the decorator could skip the functions normal signature parsing step, and call the function with the args and kwds instead. And possibly a name space could be reused with a function, which would have the effect of it having static variables, or a class with attributes used for that purpose. (Yes, care would be needed.) These functions might also be usable as stacked decorators. (It'll need some experimenting to make this work.) @func_call_code @func_make_name_space @func_parse_signatire def foo(...): ... Would just break up calling a function into sub steps. Additional decorators could be stuck between those to check or alter the intermediate results. Something like that might be useful for some types of testing. It's probably easier to check the values in the created name space before it's used than it is to check the arguments before they are parsed by the signature. If these could be C functions and corresponded to their own byte codes, they might enable some interesting internal optimisations. Just a few initial thoughts to follow up on, Ron
Joshua Landau wrote:
What about:
class Foo: bar = bar
That would be going too far, I think. I can't remember *ever* needing to write code like that in a class. On the other hand, passing function arguments received from a caller on to another function under the same names is very common. Also, it's a somewhat dubious thing to write anyway, since it relies on name lookups in a class scope working dynamically. While they currently do in CPython, I wouldn't like to rely on that always remaining the case. I'm not sure about the dictionary case. It's not strictly necessary, since if you have it for keyword arguments, you can do dict(=a, =b, =c). So I'm +1 on allowing this for function arguments, -0 for dicts, and -1 on anything else. -- Greg
On 23 June 2013 09:39, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Joshua Landau wrote:
What about:
class Foo: bar = bar
That would be going too far, I think. I can't remember *ever* needing to write code like that in a class.
I have. Like, once, though.
Also, it's a somewhat dubious thing to write anyway, since it relies on name lookups in a class scope working dynamically. While they currently do in CPython, I wouldn't like to rely on that always remaining the case.
Is this not a defined behaviour? I wouldn't expect this to change before 4.0, and that's a different ballgame. Does it break in some other implementations?
On 23 Jun 2013 23:44, "Joshua Landau" <joshua.landau.ws@gmail.com> wrote:
On 23 June 2013 09:39, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Joshua Landau wrote:
What about:
class Foo: bar = bar
That would be going too far, I think. I can't remember *ever* needing to write code like that in a class.
I have. Like, once, though.
Also, it's a somewhat dubious thing to write anyway, since it relies on name lookups in a class scope working dynamically. While they currently do in CPython, I wouldn't like to rely on that always remaining the case.
Is this not a defined behaviour? I wouldn't expect this to change before 4.0, and that's a different ballgame. Does it break in some other implementations?
It's defined behaviour. It's only function scopes which force assignment targets to be purely local. Cheers, Nick.
_______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
Joshua Landau wrote:
class Foo: bar = bar
On 23 June 2013 09:39, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Also, it's a somewhat dubious thing to write anyway, since it relies on name lookups in a class scope working dynamically. While they currently do in CPython, I wouldn't like to rely on that always remaining the case.
Is this not a defined behaviour?
According to the Language Reference, section 4.1, "Naming and binding": If a name binding operation occurs anywhere within a code block, all uses of the name within the block are treated as references to the current block. No distinction is made there between function bodies and other kinds of block. Also in that section, A class definition is an executable statement that may use and define names. These references follow the normal rules for name resolution. So I would conclude that the above code is technically illegal. -- Greg
On 22/06/13 20:27, Anders Hovmöller wrote:
Keyword arguments are great for increasing readability and making code more robust but in my opinion they are underused compared to the gains they can provide. You often end up with code like:
foo(bar=bar, baz=baz, foobaz=foobaz)
"Often"? In my experience, it's more like "very occasionally", and even then usually only one or two arguments: foo(spam, "yummy meatlike substance", foobaz=eggs)
which is less readable than the ordered argument version when the names of the variables and the keywords match. ( Here's another guy pointing out the same thing: http://stackoverflow.com/questions/7041752/any-reason-not-to-always-use-keyw... )
All very well and good, but you'll notice that in the entire discussion about keywords, with many, many examples give, there was *one* comment about matching names. In practice I don't believe this happens often enough to be more than an occasional nuisance. Not even a nuisance really. There is a moment of micro-surprise "oh look, a name is repeated" but that's all.
I have a suggestion that I believe can enable more usage of keyword arguments while still retaining almost all the brevity of ordered arguments: if the variable name to the right of the equal sign equals the keyword argument ("foo=foo") make it optional to just specify the name once ("=foo"). For completeness I suggest also make the same change for dictionaries: {'foo': foo} -> {:foo}. This change would turn the following code:
a = 1 b = 2 c = 3 d = {'a':a, 'b':b, 'c':c} foo(a=a, b=b, c=c)
into:
a = 1 b = 2 c = 3 d = {:a, :b, :c} foo(=a, =b, =c)
Ewww, that's hideous, and far worse than the (non-)problem you are trying to solve. But even if you disagree about the ugliness of code written in that form, consider what a special case you are looking at: given a function that takes an argument "eggs", there is an infinite number of potential arguments you *could* give. You can give arguments in positional form or keyword form, as literals, expressions, or as a single name. Just the names alone, there is a (near enough to) infinite number of possibilities: function(eggs=a) function(eggs=b) function(eggs=x) function(eggs=counter) function(eggs=flag) function(eggs=sequence) function(eggs=value) function(eggs=spam) function(eggs=ham) function(eggs=foo) function(eggs=bar) Out of this vast number of possible names that might be given, you wish to introduce new syntax to cover just one single special case: spam(eggs=eggs) => spam(=eggs) The Zen of Python has not one but two koans that cover this idea: py> import this [...] Explicit is better than implicit. Special cases aren't special enough to break the rules. You would invent new syntax, which requires more complexity in the parser, more tests, more documentation, yet another thing for people to learn, for such an utterly tiny and marginal decrease in typing that even if we agreed it was worthwhile in that special case, it is (in my opinion) hardly worth the effort. So: - I don't believe this is a problem that needs to be solved; - Even if it is a problem, I don't believe your proposal is a good solution. -- Steven
On Jun 22, 2013, at 12:27 PM, Anders Hovmöller wrote:
Keyword arguments are great for increasing readability and making code more robust but in my opinion they are underused compared to the gains they can provide. You often end up with code like:
foo(bar=bar, baz=baz, foobaz=foobaz)
The DRY motivation for this proposal reminds me of PEP 292 ($-strings) and flufl.i18n. In some of the earlier i18n work I did, the repetition was overwhelmingly inconvenient. Generally, it doesn't bother me, but I really hated doing things like: real_name = get_real_name() email_address = get_email_address() message = _('Hi $real_name <$email_address>').safe_substitute( real_name=real_name, email_address=email_address) not to mention the high probability of typos, and the added noise making the source harder to read. flufl.i18n then, shortens this to: real_name = get_real_name() email_address = get_email_address() message = _('Hi $real_name <$email_address>') The locals and globals are collected into the substitution dictionary, to be applied after the source string is translated. Yes, the implementation uses the dreaded sys._getframe(), but it's worth it. http://tinyurl.com/kqy3hbz http://tinyurl.com/lalxjaf Cheers, -Barry
On 24/06/2013 16:40, Barry Warsaw wrote:
On Jun 22, 2013, at 12:27 PM, Anders Hovmöller wrote:
Keyword arguments are great for increasing readability and making code more robust but in my opinion they are underused compared to the gains they can provide. You often end up with code like:
foo(bar=bar, baz=baz, foobaz=foobaz)
The DRY motivation for this proposal reminds me of PEP 292 ($-strings) and flufl.i18n. In some of the earlier i18n work I did, the repetition was overwhelmingly inconvenient. Generally, it doesn't bother me, but I really hated doing things like:
real_name = get_real_name() email_address = get_email_address() message = _('Hi $real_name <$email_address>').safe_substitute( real_name=real_name, email_address=email_address)
not to mention the high probability of typos, and the added noise making the source harder to read. flufl.i18n then, shortens this to:
real_name = get_real_name() email_address = get_email_address() message = _('Hi $real_name <$email_address>')
The locals and globals are collected into the substitution dictionary, to be applied after the source string is translated.
Yes, the implementation uses the dreaded sys._getframe(), but it's worth it.
Do you have to use sys._getframe()? Although it's a little longer: def _(message, variables): return re.sub(r"\$(\w+)", lambda m: variables[m.group(1)], message) You can then do this:
real_name = "REAL NAME" email_address = "EMAIL ADDRESS" _(m, globals()) 'Hi REAL NAME <EMAIL ADDRESS>' _(m, locals()) 'Hi REAL NAME <EMAIL ADDRESS>' def test(): real_name = "LOCAL REAL NAME" email_address = "LOCAL EMAIL ADDRESS" print(_(m, locals()))
On Jun 24, 2013, at 04:55 PM, MRAB wrote:
Do you have to use sys._getframe()?
Of course, this being Python I don't *have* to use anything. ;) But after a ton of experimentation on a huge body of code, I found that keeping the _() call sites really simple (i.e. essentially just the source strings) made for the most readable API. -Barry
participants (19)
-
Anders Hovmöller
-
Andrew Barnert
-
Andrew McNabb
-
Barry Warsaw
-
Chris Angelico
-
Eric V. Smith
-
Ethan Furman
-
Greg Ewing
-
Jared Grubb
-
Joao S. O. Bueno
-
Joshua Landau
-
MRAB
-
Nick Coghlan
-
Ron Adam
-
Ryan Hiebert
-
Stephen J. Turnbull
-
Stephen J. Turnbull
-
Steven D'Aprano
-
Terry Reedy