Attribute-Getter Syntax Proposal

Don't know if this has been suggested before. Instead of writing something like
map(lambda x: x.upper(), ['a', 'b', 'c'])
I suggest this syntax:
map(.upper(), ['a', 'b', 'c'])
This would also work for attributes:
map(.real, [1j, 2, 3+4j])
Internally, this would require translating .attribute -> lambda x: x.attribute and .method(*args, **kwargs) -> lambda x: x.method(*args, **kwargs) This translation should only take place where a "normal" attribute lookup makes no sense (throws a SyntaxError); i.e. foo.bar works as before, foo(.bar) would previously throw a SyntaxError, so the new syntax applies and the .bar is interpreted as an attrgetter. This is of course only a cosmetic improvement over operator.attrgetter and operator.methodcaller, but I think it's nice enough to warrant consideration. If you like this idea or think it's utter garbage, feel free to discuss.

Hi Samuel Interesting idea, and certainly addresses a real problem, if you find yourself creating lots of lambda expressions. But in my first opinion, not so useful that it merits adding to the syntax of Python. (Even if I never use it, it puts an extra burden on me when scanning Python code. Something that used to look like a syntax error is now valid. That's more work for me.) However, you can already achieve something similar, and perhaps more expressive. It is possible to define an object 'magic' such that fn = magic.upper fn = lambda x: x.upper() are effectively equivalent. And this can be done now. No need for a PEP and a new version of Python. And available for those who have to use some fixed already existing Python versions. I hope you'd be interesting in coding this up yourself. I'd have a limited amount of time to help you, but it would put you on a good learning curve, for fundamentals of the Python object model. -- Jonathan

This was actually quite interesting to code, thanks for the idea Jonathan! You can even support "magic.upper()" and "magic.real" at the same time as well as "magic[0]": class MagicClass: NO_ARG = object() @staticmethod def __getattribute__(attr): def method(x=MagicClass.NO_ARG): if x is MagicClass.NO_ARG: return lambda x: getattr(x, attr)() return getattr(x, attr) return method @staticmethod def __getitem__(attr): return lambda x: x[attr] magic = MagicClass() print(list(map(magic.upper(), ["abc", "def"]))) # ['ABC', 'DEF'] print(list(map(magic.real, [1j, 2, 3+4j]))) # [0.0, 2, 3.0] print(list(map(magic[0], ["abc", "def"]))) # ['a', 'd'] You could also use None instead of that NO_ARG thingy, because you most likely won't want to get any attributes of None objects, but that wouldn't produce proper errors incase you do anyways. With metaclasses you propably could also make it work directly on the class without the need of a magic instance. Benedikt Am 08.03.2019 um 19:07 schrieb Jonathan Fine:

On Sat, Mar 9, 2019 at 9:09 AM Benedikt Werner <1benediktwerner@gmail.com> wrote:
Rather than using map in this way, I would recommend a list comprehension: print([x.upper() for x in ["abc", "def"]]) print([x.real for x in [1j, 2, 3+4j]]) print([x[0] for x in ["abc", "def"]]) No magic needed. ChrisA

On Fri, Mar 08, 2019 at 03:43:10PM -0800, Chris Barker - NOAA Federal via Python-ideas wrote:
Comprehensions are great for avoiding the need to write verbose lambdas before calling map: map(lambda x: x + 1, numbers) (x + 1 for x in numbers) but you typically only save a few characters, and you don't even save that when the function already exists: map(str.upper, strings) (s.upper() for s in strings) So horses for courses. In my opinion, map() looks nicer when you are calling a pre-existing named function, and comprehensions look nicer when you have an expression involving operators which would otherwise require a lambda. -- Steven

If we were going to add a syntax for abbreviating lambdas, I would rather see something more generally useful, e.g. x -> x.method() as an abbrevation for lambda x: x.method() -- Greg

On Sat, Mar 09, 2019 at 05:19:50PM +1300, Greg Ewing wrote:
Cocnut does this! https://coconut.readthedocs.io/en/latest/DOCS.html#lambdas It allows any arbitrary expression and parameter list, rather than being limited to a single special case: lambda x, y=None, *args, **kw: spam(x)+y.eggs()-len(args)+kw['foo'] (x, y=None, *args, **kw) -> spam(x)+y.eggs()-len(args)+kw['foo'] # Saves an entire three columns! *wink* (I believe this is similar to Haskell's syntax.) Given that we have lambda though, and many people don't like anonymous functions, I doubt there's enough advantage to justify adding new syntax. I prefer the look of -> to lambda, but given that we have lambda already I wouldn't want to waste that nice looking arrow operator on something we already can do. I'd rather see it saved for something more interesting, like a hypothetical cascading (fluent) method call syntax, or pattern matching. -- Steven

Samuel Li wrote:
.attribute -> lambda x: x.attribute
.method(*args, **kwargs) -> lambda x: x.method(*args, **kwargs)
Leading dots can be hard to spot when reading code. Also, I'm not convinced that use cases for this are frequent enough to warrant new syntax. Something akin to this can already be done in simple cases: map(string.upper, some_list) Anything more complicated, such as passing arguments, is probably better expressed with a comprehension. -- Greg

You could use the time machine: https://docs.python.org/3/library/operator.html On Fri, Mar 8, 2019, 11:57 AM Samuel Li <samuel.wgx@gmail.com> wrote:

You can do : I suggest this syntax:
map(.upper(), ['a', 'b', 'c'])
map(dot('upper'), 'a b c'.split()) map(dot('replace', 'x', 'y'), 'xo do ox'.split()) def dot(name, *args, **kwargs): return lambda self: getattr(self, name)(*args, **kwargs)
This would also work for attributes:
map(.real, [1j, 2, 3+4j])
from operator import itemgetter map(itergetter('real'), [...]) from operator import itemgetter as gatt map(itergetter('real'), [...]) Also, check out my package funcoperators on pip for neat functional programming syntaxes https://pypi.org/project/funcoperators/

Hi Samuel Interesting idea, and certainly addresses a real problem, if you find yourself creating lots of lambda expressions. But in my first opinion, not so useful that it merits adding to the syntax of Python. (Even if I never use it, it puts an extra burden on me when scanning Python code. Something that used to look like a syntax error is now valid. That's more work for me.) However, you can already achieve something similar, and perhaps more expressive. It is possible to define an object 'magic' such that fn = magic.upper fn = lambda x: x.upper() are effectively equivalent. And this can be done now. No need for a PEP and a new version of Python. And available for those who have to use some fixed already existing Python versions. I hope you'd be interesting in coding this up yourself. I'd have a limited amount of time to help you, but it would put you on a good learning curve, for fundamentals of the Python object model. -- Jonathan

This was actually quite interesting to code, thanks for the idea Jonathan! You can even support "magic.upper()" and "magic.real" at the same time as well as "magic[0]": class MagicClass: NO_ARG = object() @staticmethod def __getattribute__(attr): def method(x=MagicClass.NO_ARG): if x is MagicClass.NO_ARG: return lambda x: getattr(x, attr)() return getattr(x, attr) return method @staticmethod def __getitem__(attr): return lambda x: x[attr] magic = MagicClass() print(list(map(magic.upper(), ["abc", "def"]))) # ['ABC', 'DEF'] print(list(map(magic.real, [1j, 2, 3+4j]))) # [0.0, 2, 3.0] print(list(map(magic[0], ["abc", "def"]))) # ['a', 'd'] You could also use None instead of that NO_ARG thingy, because you most likely won't want to get any attributes of None objects, but that wouldn't produce proper errors incase you do anyways. With metaclasses you propably could also make it work directly on the class without the need of a magic instance. Benedikt Am 08.03.2019 um 19:07 schrieb Jonathan Fine:

On Sat, Mar 9, 2019 at 9:09 AM Benedikt Werner <1benediktwerner@gmail.com> wrote:
Rather than using map in this way, I would recommend a list comprehension: print([x.upper() for x in ["abc", "def"]]) print([x.real for x in [1j, 2, 3+4j]]) print([x[0] for x in ["abc", "def"]]) No magic needed. ChrisA

On Fri, Mar 08, 2019 at 03:43:10PM -0800, Chris Barker - NOAA Federal via Python-ideas wrote:
Comprehensions are great for avoiding the need to write verbose lambdas before calling map: map(lambda x: x + 1, numbers) (x + 1 for x in numbers) but you typically only save a few characters, and you don't even save that when the function already exists: map(str.upper, strings) (s.upper() for s in strings) So horses for courses. In my opinion, map() looks nicer when you are calling a pre-existing named function, and comprehensions look nicer when you have an expression involving operators which would otherwise require a lambda. -- Steven

If we were going to add a syntax for abbreviating lambdas, I would rather see something more generally useful, e.g. x -> x.method() as an abbrevation for lambda x: x.method() -- Greg

On Sat, Mar 09, 2019 at 05:19:50PM +1300, Greg Ewing wrote:
Cocnut does this! https://coconut.readthedocs.io/en/latest/DOCS.html#lambdas It allows any arbitrary expression and parameter list, rather than being limited to a single special case: lambda x, y=None, *args, **kw: spam(x)+y.eggs()-len(args)+kw['foo'] (x, y=None, *args, **kw) -> spam(x)+y.eggs()-len(args)+kw['foo'] # Saves an entire three columns! *wink* (I believe this is similar to Haskell's syntax.) Given that we have lambda though, and many people don't like anonymous functions, I doubt there's enough advantage to justify adding new syntax. I prefer the look of -> to lambda, but given that we have lambda already I wouldn't want to waste that nice looking arrow operator on something we already can do. I'd rather see it saved for something more interesting, like a hypothetical cascading (fluent) method call syntax, or pattern matching. -- Steven

Samuel Li wrote:
.attribute -> lambda x: x.attribute
.method(*args, **kwargs) -> lambda x: x.method(*args, **kwargs)
Leading dots can be hard to spot when reading code. Also, I'm not convinced that use cases for this are frequent enough to warrant new syntax. Something akin to this can already be done in simple cases: map(string.upper, some_list) Anything more complicated, such as passing arguments, is probably better expressed with a comprehension. -- Greg

You could use the time machine: https://docs.python.org/3/library/operator.html On Fri, Mar 8, 2019, 11:57 AM Samuel Li <samuel.wgx@gmail.com> wrote:

You can do : I suggest this syntax:
map(.upper(), ['a', 'b', 'c'])
map(dot('upper'), 'a b c'.split()) map(dot('replace', 'x', 'y'), 'xo do ox'.split()) def dot(name, *args, **kwargs): return lambda self: getattr(self, name)(*args, **kwargs)
This would also work for attributes:
map(.real, [1j, 2, 3+4j])
from operator import itemgetter map(itergetter('real'), [...]) from operator import itemgetter as gatt map(itergetter('real'), [...]) Also, check out my package funcoperators on pip for neat functional programming syntaxes https://pypi.org/project/funcoperators/
participants (10)
-
Benedikt Werner
-
Brett Cannon
-
Chris Angelico
-
Chris Barker - NOAA Federal
-
David Mertz
-
Greg Ewing
-
Jonathan Fine
-
Robert Vanden Eynde
-
Samuel Li
-
Steven D'Aprano